Merge branch 'maint'
authorAndre Noll <maan@tuebingen.mpg.de>
Thu, 4 Nov 2021 16:10:34 +0000 (17:10 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Thu, 4 Nov 2021 16:10:55 +0000 (17:10 +0100)
* maint:
  paraslash 0.5.9

264 files changed:
.gitignore
Doxyfile
INSTALL
Makefile.in
Makefile.real
NEWS.md
aac.h [deleted file]
aac_afh.c
aac_common.c [deleted file]
aacdec_filter.c
acl.c
acl.h
afh.c
afh.h
afh_common.c
afh_recv.c
afs.c
afs.cmd [deleted file]
afs.h
aft.c
alsa_mix.c
alsa_write.c
amp_filter.c
ao_write.c
attribute.c
audioc.c
audiod.c
audiod.cmd [deleted file]
audiod.h
audiod_command.c
autogen.sh
base64.h
bash_completion
bitstream.c
bitstream.h
blob.c
buffer_tree.c
buffer_tree.h
check_wav.c
check_wav.h
chunk_queue.c
chunk_queue.h
client.c
client.h
client_common.c
close_on_fork.c
close_on_fork.h
command.c
command.h
command_util.bash [deleted file]
compress_filter.c
configure.ac
crypt.c [deleted file]
crypt.h
crypt_backend.h
crypt_common.c
daemon.c
daemon.h
dccp_recv.c
dccp_send.c
error.h
exec.c
fade.c [deleted file]
fd.c
fd.h
fecdec_filter.c
file_write.c
filter.c
filter.h
filter_common.c
flac_afh.c
flacdec_filter.c
gcrypt.c
ggo.c [deleted file]
ggo.h [deleted file]
grab_client.c
grab_client.h
gui.c
gui.h
gui_theme.c
http_recv.c
http_send.c
imdct.c
interactive.c
interactive.h
ipc.c
ipc.h
lsu.c [new file with mode: 0644]
lsu.h [new file with mode: 0644]
m4/gengetopt/afh.m4 [deleted file]
m4/gengetopt/afh_recv.m4 [deleted file]
m4/gengetopt/alsa_write.m4 [deleted file]
m4/gengetopt/amp_filter.m4 [deleted file]
m4/gengetopt/ao_write.m4 [deleted file]
m4/gengetopt/audioc.m4 [deleted file]
m4/gengetopt/audiod.m4 [deleted file]
m4/gengetopt/channels.m4 [deleted file]
m4/gengetopt/client.m4 [deleted file]
m4/gengetopt/color.m4 [deleted file]
m4/gengetopt/complete.m4 [deleted file]
m4/gengetopt/compress_filter.m4 [deleted file]
m4/gengetopt/config_file.m4 [deleted file]
m4/gengetopt/daemon.m4 [deleted file]
m4/gengetopt/dccp_recv.m4 [deleted file]
m4/gengetopt/fade.m4 [deleted file]
m4/gengetopt/file_write.m4 [deleted file]
m4/gengetopt/filter.m4 [deleted file]
m4/gengetopt/group.m4 [deleted file]
m4/gengetopt/gui.m4 [deleted file]
m4/gengetopt/header.m4 [deleted file]
m4/gengetopt/history_file.m4 [deleted file]
m4/gengetopt/http_recv.m4 [deleted file]
m4/gengetopt/log_timing.m4 [deleted file]
m4/gengetopt/logfile.m4 [deleted file]
m4/gengetopt/loglevel.m4 [deleted file]
m4/gengetopt/makefile [deleted file]
m4/gengetopt/mp3dec_filter.m4 [deleted file]
m4/gengetopt/oss_write.m4 [deleted file]
m4/gengetopt/osx_write.m4 [deleted file]
m4/gengetopt/play.m4 [deleted file]
m4/gengetopt/prebuffer_filter.m4 [deleted file]
m4/gengetopt/priority.m4 [deleted file]
m4/gengetopt/recv.m4 [deleted file]
m4/gengetopt/resample_filter.m4 [deleted file]
m4/gengetopt/sample_format.m4 [deleted file]
m4/gengetopt/sample_rate.m4 [deleted file]
m4/gengetopt/server.m4 [deleted file]
m4/gengetopt/sync_filter.m4 [deleted file]
m4/gengetopt/udp_recv.m4 [deleted file]
m4/gengetopt/user.m4 [deleted file]
m4/gengetopt/write.m4 [deleted file]
m4/lls/afh.suite.m4 [new file with mode: 0644]
m4/lls/audioc.suite.m4 [new file with mode: 0644]
m4/lls/audiod.suite.m4 [new file with mode: 0644]
m4/lls/audiod_cmd.suite.m4 [new file with mode: 0644]
m4/lls/client.suite.m4 [new file with mode: 0644]
m4/lls/copyright.m4 [new file with mode: 0644]
m4/lls/filter.suite.m4 [new file with mode: 0644]
m4/lls/filter_cmd.suite.m4 [new file with mode: 0644]
m4/lls/gui.suite.m4 [new file with mode: 0644]
m4/lls/include/channels.m4 [new file with mode: 0644]
m4/lls/include/color.m4 [new file with mode: 0644]
m4/lls/include/common-option-section.m4 [new file with mode: 0644]
m4/lls/include/complete.m4 [new file with mode: 0644]
m4/lls/include/config-file.m4 [new file with mode: 0644]
m4/lls/include/daemon.m4 [new file with mode: 0644]
m4/lls/include/detailed-help.m4 [new file with mode: 0644]
m4/lls/include/group.m4 [new file with mode: 0644]
m4/lls/include/help.m4 [new file with mode: 0644]
m4/lls/include/history-file.m4 [new file with mode: 0644]
m4/lls/include/host.m4 [new file with mode: 0644]
m4/lls/include/log-timing.m4 [new file with mode: 0644]
m4/lls/include/logfile.m4 [new file with mode: 0644]
m4/lls/include/loglevel.m4 [new file with mode: 0644]
m4/lls/include/long-help.m4 [new file with mode: 0644]
m4/lls/include/per-command-options-section.m4 [new file with mode: 0644]
m4/lls/include/port.m4 [new file with mode: 0644]
m4/lls/include/priority.m4 [new file with mode: 0644]
m4/lls/include/sample-format.m4 [new file with mode: 0644]
m4/lls/include/sample-rate.m4 [new file with mode: 0644]
m4/lls/include/user.m4 [new file with mode: 0644]
m4/lls/include/version.m4 [new file with mode: 0644]
m4/lls/makefile [new file with mode: 0644]
m4/lls/mixer.suite.m4 [new file with mode: 0644]
m4/lls/play.suite.m4 [new file with mode: 0644]
m4/lls/play_cmd.suite.m4 [new file with mode: 0644]
m4/lls/recv.suite.m4 [new file with mode: 0644]
m4/lls/recv_cmd.suite.m4 [new file with mode: 0644]
m4/lls/server.suite.m4 [new file with mode: 0644]
m4/lls/server_cmd.suite.m4 [new file with mode: 0644]
m4/lls/write.suite.m4 [new file with mode: 0644]
m4/lls/write_cmd.suite.m4 [new file with mode: 0644]
man_util.bash [deleted file]
mix.h
mixer.c [new file with mode: 0644]
mm.c
mm.h
mood.c
mood.h
mp.c [new file with mode: 0644]
mp.h [new file with mode: 0644]
mp3_afh.c
mp3dec_filter.c
net.c
net.h
ogg_afh.c
ogg_afh_common.c
ogg_afh_common.h
oggdec_filter.c
openssl.c [new file with mode: 0644]
opus_afh.c
opus_common.h
opusdec_filter.c
oss_mix.c
oss_write.c
osx_write.c [deleted file]
para.h
play.c
play.cmd [deleted file]
playlist.c
portable_io.h
prebuffer_filter.c
recv.c
recv.h
recv_common.c
resample_filter.c
ringbuffer.c
ringbuffer.h
sched.c
sched.h
score.c
send.h
send_common.c
server.c
server.cmd [deleted file]
server.h
sideband.c
sideband.h
signal.c
signal.h
spx.h
spx_afh.c
spx_common.c
spxdec_filter.c
stat.c
stdin.c
stdin.h
stdout.c
stdout.h
string.c
string.h
sync_filter.c
t/makefile.test
t/t0001-oggdec-correctness.sh
t/t0004-server.sh
t/t0005-man.sh
t/test-lib.sh
time.c
udp_recv.c
udp_send.c
user_list.c
user_list.h
version.c
vss.c
vss.h
wav_filter.c
web/.htaccess
web/about.in.html
web/dia/overview.dia [deleted file]
web/documentation.in.html
web/download.in.html
web/images/paraslash.ico
web/images/paraslash.png
web/manual.md
wma_afh.c
wma_common.c
wmadec_filter.c
write.c
write.h
write_common.c
write_common.h [deleted file]
yy/makefile [new file with mode: 0644]
yy/mp.lex [new file with mode: 0644]
yy/mp.y [new file with mode: 0644]

index 10a7a8a91d570d5d9d16a18c1c1d896334adc7b4..bd5e04801c4bec2714dd515810296f1bc89cbab0 100644 (file)
@@ -15,7 +15,7 @@ config.status
 Makefile
 TODO
 paraslash-*.tar.bz2
-web/dia/overview.pdf
+paraslash-*.tar.xz
 *.swp
 *.rej
 *~
index c607b8eab59985d84cc8ac382536c4d45e76890d..b11683e424443baef8754b34097ad3cb8dbed4fe 100644 (file)
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,4 +1,4 @@
-# Doxyfile 1.8.6
+# Doxyfile 1.8.11
 
 # This file describes the settings to be used by the documentation system
 # doxygen (www.doxygen.org) for a project.
@@ -46,10 +46,10 @@ PROJECT_NUMBER         =
 
 PROJECT_BRIEF          =
 
-# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
-# the documentation. The maximum height of the logo should not exceed 55 pixels
-# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
-# to the output directory.
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
 
 PROJECT_LOGO           =
 
@@ -60,7 +60,7 @@ PROJECT_LOGO           =
 
 OUTPUT_DIRECTORY       = web_sync/doxygen
 
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
 # directories (in 2 levels) under the output directory of each output format and
 # will distribute the generated files over these directories. Enabling this
 # option can be useful when feeding doxygen a huge amount of source files, where
@@ -70,6 +70,14 @@ OUTPUT_DIRECTORY       = web_sync/doxygen
 
 CREATE_SUBDIRS         = NO
 
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
 # The OUTPUT_LANGUAGE tag is used to specify the language in which all
 # documentation generated by doxygen is written. Doxygen will use this
 # information to generate all constant output in the proper language.
@@ -85,14 +93,14 @@ CREATE_SUBDIRS         = NO
 
 OUTPUT_LANGUAGE        = English
 
-# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# 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.
 # The default value is: YES.
 
 BRIEF_MEMBER_DESC      = YES
 
-# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
 # description of a member or function before the detailed description
 #
 # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
@@ -127,7 +135,7 @@ ALWAYS_DETAILED_SEC    = NO
 
 INLINE_INHERITED_MEMB  = NO
 
-# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
 # before files name in the file list and in the header files. If set to NO the
 # shortest path that makes the file name unique will be used
 # The default value is: YES.
@@ -197,9 +205,9 @@ MULTILINE_CPP_IS_BRIEF = NO
 
 INHERIT_DOCS           = YES
 
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
-# new page for each member. If set to NO, the documentation of a member will be
-# part of the file/class/namespace that contains it.
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
 # The default value is: NO.
 
 SEPARATE_MEMBER_PAGES  = NO
@@ -261,11 +269,14 @@ OPTIMIZE_OUTPUT_VHDL   = NO
 # 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, 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.
+# 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.
 #
-# Note For files without extension you can use no_extension as a placeholder.
+# Note: For files without extension you can use no_extension as a placeholder.
 #
 # Note that for custom extensions you also need to set FILE_PATTERNS otherwise
 # the files are not read by doxygen.
@@ -284,8 +295,8 @@ MARKDOWN_SUPPORT       = YES
 
 # 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 by putting a % sign in front of the word
-# or globally by setting AUTOLINK_SUPPORT to NO.
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
 # The default value is: YES.
 
 AUTOLINK_SUPPORT       = YES
@@ -325,13 +336,20 @@ SIP_SUPPORT            = NO
 IDL_PROPERTY_SUPPORT   = YES
 
 # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
+# tag is set to YES then doxygen will reuse the documentation of the first
 # member in the group (if any) for the other members of the group. By default
 # all members of a group must be documented explicitly.
 # The default value is: NO.
 
 DISTRIBUTE_GROUP_DOC   = NO
 
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
 # Set the SUBGROUPING tag to YES to allow class member groups of the same type
 # (for instance a group of public functions) to be put as a subgroup of that
 # type (e.g. under the Public Functions section). Set it to NO to prevent
@@ -390,7 +408,7 @@ LOOKUP_CACHE_SIZE      = 0
 # Build related configuration options
 #---------------------------------------------------------------------------
 
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
 # documentation are documented, even if no documentation was available. Private
 # class members and static file members will be hidden unless the
 # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
@@ -400,35 +418,35 @@ LOOKUP_CACHE_SIZE      = 0
 
 EXTRACT_ALL            = YES
 
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
 # be included in the documentation.
 # The default value is: NO.
 
 EXTRACT_PRIVATE        = NO
 
-# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# 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.
 
 EXTRACT_PACKAGE        = NO
 
-# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
 # included in the documentation.
 # The default value is: NO.
 
 EXTRACT_STATIC         = NO
 
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
-# locally in source files will be included in the documentation. If set to NO
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
 # only classes defined in header files are included. Does not have any effect
 # for Java sources.
 # The default value is: YES.
 
 EXTRACT_LOCAL_CLASSES  = NO
 
-# This flag is only useful for Objective-C code. When set to YES local methods,
+# This flag is only useful for Objective-C code. If set to YES, local methods,
 # which are defined in the implementation section but not in the interface are
-# included in the documentation. If set to NO only methods in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
 # included.
 # The default value is: NO.
 
@@ -453,21 +471,21 @@ HIDE_UNDOC_MEMBERS     = NO
 
 # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
 # undocumented classes that are normally visible in the class hierarchy. If set
-# to NO these classes will be included in the various overviews. This option has
-# no effect if EXTRACT_ALL is enabled.
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
 # The default value is: 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
+# (class|struct|union) declarations. If set to NO, these declarations will be
 # included in the documentation.
 # The default value is: NO.
 
 HIDE_FRIEND_COMPOUNDS  = NO
 
 # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
-# documentation blocks found inside the body of a function. If set to NO these
+# documentation blocks found inside the body of a function. If set to NO, these
 # blocks will be appended to the function's detailed documentation block.
 # The default value is: NO.
 
@@ -481,7 +499,7 @@ HIDE_IN_BODY_DOCS      = NO
 INTERNAL_DOCS          = NO
 
 # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
-# names in lower-case letters. If set to YES upper-case letters are also
+# 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.
@@ -490,12 +508,19 @@ INTERNAL_DOCS          = NO
 CASE_SENSE_NAMES       = YES
 
 # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
-# their full class and namespace scopes in the documentation. If set to YES the
+# their full class and namespace scopes in the documentation. If set to YES, the
 # scope will be hidden.
 # The default value is: NO.
 
 HIDE_SCOPE_NAMES       = YES
 
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
 # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
 # the files that are included by a file in the documentation of that file.
 # The default value is: YES.
@@ -523,14 +548,14 @@ INLINE_INFO            = YES
 
 # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
 # (detailed) documentation of file and class members alphabetically by member
-# name. If set to NO the members will appear in declaration order.
+# name. If set to NO, the members will appear in declaration order.
 # The default value is: YES.
 
 SORT_MEMBER_DOCS       = NO
 
 # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
 # descriptions of file, namespace and class members alphabetically by member
-# name. If set to NO the members will appear in declaration order. Note that
+# name. If set to NO, the members will appear in declaration order. Note that
 # this will also influence the order of the classes in the class list.
 # The default value is: NO.
 
@@ -575,27 +600,25 @@ SORT_BY_SCOPE_NAME     = NO
 
 STRICT_PROTO_MATCHING  = NO
 
-# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
-# todo list. This list is created by putting \todo commands in the
-# documentation.
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
 # The default value is: YES.
 
 GENERATE_TODOLIST      = YES
 
-# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
-# test list. This list is created by putting \test commands in the
-# documentation.
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
 # The default value is: YES.
 
 GENERATE_TESTLIST      = YES
 
-# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
 # list. This list is created by putting \bug commands in the documentation.
 # The default value is: YES.
 
 GENERATE_BUGLIST       = YES
 
-# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
 # the deprecated list. This list is created by putting \deprecated commands in
 # the documentation.
 # The default value is: YES.
@@ -620,8 +643,8 @@ ENABLED_SECTIONS       =
 MAX_INITIALIZER_LINES  = 30
 
 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
-# the bottom of the documentation of classes and structs. If set to YES the list
-# will mention the files that were used to generate the documentation.
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
 # The default value is: YES.
 
 SHOW_USED_FILES        = YES
@@ -669,8 +692,7 @@ LAYOUT_FILE            =
 # to be installed. See also http://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. Do not use file names with spaces, bibtex cannot handle them. See
-# also \cite for info how to create references.
+# search path. See also \cite for info how to create references.
 
 CITE_BIB_FILES         =
 
@@ -686,7 +708,7 @@ CITE_BIB_FILES         =
 QUIET                  = YES
 
 # The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
 # this implies that the warnings are on.
 #
 # Tip: Turn warnings on while writing the documentation.
@@ -694,7 +716,7 @@ QUIET                  = YES
 
 WARNINGS               = YES
 
-# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
 # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
 # will automatically be disabled.
 # The default value is: YES.
@@ -711,12 +733,18 @@ 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.
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
 # The default value is: NO.
 
 WARN_NO_PARAMDOC       = YES
 
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
 # The WARN_FORMAT tag determines the format of the warning messages that doxygen
 # can produce. The string should contain the $file, $line, and $text tags, which
 # will be replaced by the file and line number from which the warning originated
@@ -740,7 +768,7 @@ WARN_LOGFILE           =
 # The INPUT tag is used to specify the files and/or directories that contain
 # documented source files. You may enter file names like myfile.cpp or
 # directories like /usr/src/myproject. Separate the files or directories with
-# spaces.
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
 INPUT                  = .
@@ -756,12 +784,17 @@ INPUT_ENCODING         = UTF-8
 
 # If the value of the INPUT tag contains directories, you can use the
 # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
-# *.h) to filter out the source-files in the directories. 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, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
-# *.qsf, *.as and *.js.
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# 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.
 
 FILE_PATTERNS          = *.c \
                          *.h
@@ -779,7 +812,7 @@ RECURSIVE              = NO
 # Note that relative paths are relative to the directory from which doxygen is
 # run.
 
-EXCLUDE                =
+EXCLUDE                = config.h
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded
@@ -795,10 +828,7 @@ EXCLUDE_SYMLINKS       = NO
 # Note that the wildcards are matched against the file with absolute path, so to
 # exclude all test directories for example use the pattern */test/*
 
-EXCLUDE_PATTERNS       = *.cmdline.* \
-                         gcc-compat.h \
-                         *.command_list.h \
-                         *.completion.h
+EXCLUDE_PATTERNS       = gcc-compat.h
 
 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
 # (namespaces, classes, functions, etc.) that should be excluded from the
@@ -851,6 +881,10 @@ IMAGE_PATH             =
 # Note that the filter must not add or remove lines; it is applied before the
 # code is scanned, but not when the output code is generated. If lines are added
 # or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
 
 INPUT_FILTER           =
 
@@ -860,11 +894,15 @@ INPUT_FILTER           =
 # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
 # filters are used. If the FILTER_PATTERNS tag is empty or if none of the
 # patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
 
 FILTER_PATTERNS        =
 
 # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER ) will also be used to filter the input files that are used for
+# INPUT_FILTER) will also be used to filter the input files that are used for
 # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
 # The default value is: NO.
 
@@ -896,7 +934,7 @@ USE_MDFILE_AS_MAINPAGE =
 # also VERBATIM_HEADERS is set to NO.
 # The default value is: NO.
 
-SOURCE_BROWSER         = YES
+SOURCE_BROWSER         = NO
 
 # Setting the INLINE_SOURCES tag to YES will include the body of functions,
 # classes and enums directly into the documentation.
@@ -924,7 +962,7 @@ REFERENCED_BY_RELATION = YES
 REFERENCES_RELATION    = YES
 
 # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
-# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
 # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
 # link to the documentation.
 # The default value is: YES.
@@ -961,7 +999,7 @@ SOURCE_TOOLTIPS        = YES
 # The default value is: NO.
 # This tag requires that the tag SOURCE_BROWSER is set to YES.
 
-USE_HTAGS              = YES
+USE_HTAGS              = NO
 
 # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
 # verbatim copy of the header file for each class for which an include is
@@ -969,7 +1007,7 @@ USE_HTAGS              = YES
 # See also: Section \class.
 # The default value is: YES.
 
-VERBATIM_HEADERS       = YES
+VERBATIM_HEADERS       = NO
 
 #---------------------------------------------------------------------------
 # Configuration options related to the alphabetical class index
@@ -1001,7 +1039,7 @@ IGNORE_PREFIX          =
 # Configuration options related to the HTML output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
 # The default value is: YES.
 
 GENERATE_HTML          = YES
@@ -1063,13 +1101,15 @@ HTML_FOOTER            = web/footer.html
 
 HTML_STYLESHEET        =
 
-# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-
-# defined cascading style sheet that is included after the standard style sheets
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
 # created by doxygen. Using this option one can overrule certain style aspects.
 # This is preferred over using HTML_STYLESHEET since it does not replace the
-# standard style sheet and is therefor more robust against future updates.
-# Doxygen will copy the style sheet file to the output directory. For an example
-# see the documentation.
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_EXTRA_STYLESHEET  =
@@ -1085,7 +1125,7 @@ HTML_EXTRA_STYLESHEET  =
 HTML_EXTRA_FILES       =
 
 # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
-# will adjust the colors in the stylesheet and background images according to
+# 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
 # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
@@ -1116,8 +1156,9 @@ HTML_COLORSTYLE_GAMMA  = 80
 
 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
 # page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: YES.
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
 HTML_TIMESTAMP         = YES
@@ -1213,28 +1254,29 @@ GENERATE_HTMLHELP      = NO
 CHM_FILE               =
 
 # The HHC_LOCATION tag can be used to specify the location (absolute path
-# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
 # doxygen will try to run the HTML help compiler on the generated index.hhp.
 # The file has to be specified with full path.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 HHC_LOCATION           =
 
-# The GENERATE_CHI flag controls if a separate .chi index file is generated (
-# YES) or that it should be included in the master .chm file ( NO).
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 GENERATE_CHI           = NO
 
-# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
 # and project file content.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
 CHM_INDEX_ENCODING     =
 
-# The BINARY_TOC flag controls whether a binary table of contents is generated (
-# YES) or a normal table of contents ( NO) in the .chm file.
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
@@ -1347,7 +1389,7 @@ DISABLE_INDEX          = NO
 # index structure (just like the one that is generated for HTML Help). For this
 # to work a browser that supports JavaScript, DHTML, CSS and frames is required
 # (i.e. any modern browser). Windows users are probably better off using the
-# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
 # further fine-tune the look of the index. As an example, the default style
 # sheet generated by doxygen has an example that shows how to put an image at
 # the root of the tree instead of the PROJECT_NAME. Since the tree basically has
@@ -1375,7 +1417,7 @@ ENUM_VALUES_PER_LINE   = 4
 
 TREEVIEW_WIDTH         = 250
 
-# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
 # external symbols imported via tag files in a separate window.
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1404,7 +1446,7 @@ FORMULA_TRANSPARENT    = YES
 
 # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
 # http://www.mathjax.org) which uses client side Javascript for the rendering
-# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# 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
 # to it using the MATHJAX_RELPATH option.
@@ -1474,11 +1516,11 @@ 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
-# are two flavours 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 and searching needs to be provided by external tools. See
-# the section "External Indexing and Searching" for details.
+# 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
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
 # The default value is: NO.
 # This tag requires that the tag SEARCHENGINE is set to YES.
 
@@ -1490,7 +1532,7 @@ SERVER_BASED_SEARCH    = NO
 # external search engine pointed to by the SEARCHENGINE_URL option to obtain the
 # search results.
 #
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# 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/).
 #
@@ -1503,7 +1545,7 @@ EXTERNAL_SEARCH        = NO
 # The SEARCHENGINE_URL should point to a search engine hosted by a web server
 # which will return the search results when EXTERNAL_SEARCH is enabled.
 #
-# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# 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
 # Searching" for details.
@@ -1541,7 +1583,7 @@ EXTRA_SEARCH_MAPPINGS  =
 # Configuration options related to the LaTeX output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
 # The default value is: YES.
 
 GENERATE_LATEX         = NO
@@ -1572,7 +1614,7 @@ LATEX_CMD_NAME         = latex
 
 MAKEINDEX_CMD_NAME     = makeindex
 
-# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# 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.
 # The default value is: NO.
@@ -1590,9 +1632,12 @@ COMPACT_LATEX          = NO
 PAPER_TYPE             = a4wide
 
 # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
-# that should be included in the LaTeX output. To get the times font for
-# instance you can specify
-# EXTRA_PACKAGES=times
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
 # If left blank no extra packages will be included.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
@@ -1606,23 +1651,36 @@ EXTRA_PACKAGES         =
 #
 # Note: Only use a user-defined header if you know what you are doing! The
 # following commands have a special meaning inside the header: $title,
-# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will
-# replace them by respectively the title of the page, the current date and time,
-# only the current date, the version number of doxygen, the project name (see
-# PROJECT_NAME), or the project number (see PROJECT_NUMBER).
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_HEADER           =
 
 # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
 # generated LaTeX document. The footer should contain everything after the last
-# chapter. If it is left blank doxygen will generate a standard footer.
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
 #
 # Note: Only use a user-defined footer if you know what you are doing!
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_FOOTER           =
 
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
 # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
 # other source files which should be copied to the LATEX_OUTPUT output
 # directory. Note that the files will be copied as-is; there are no commands or
@@ -1640,8 +1698,8 @@ LATEX_EXTRA_FILES      =
 
 PDF_HYPERLINKS         = NO
 
-# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
-# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
 # higher quality PDF documentation.
 # The default value is: YES.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
@@ -1682,11 +1740,19 @@ LATEX_SOURCE_CODE      = NO
 
 LATEX_BIB_STYLE        = plain
 
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
 #---------------------------------------------------------------------------
 # Configuration options related to the RTF output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
 # RTF output is optimized for Word 97 and may not look too pretty with other RTF
 # readers/editors.
 # The default value is: NO.
@@ -1701,7 +1767,7 @@ GENERATE_RTF           = NO
 
 RTF_OUTPUT             = rtf
 
-# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
 # documents. This may be useful for small projects and may help to save some
 # trees in general.
 # The default value is: NO.
@@ -1738,11 +1804,21 @@ RTF_STYLESHEET_FILE    =
 
 RTF_EXTENSIONS_FILE    =
 
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
 #---------------------------------------------------------------------------
 # Configuration options related to the man page output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
 # classes and files.
 # The default value is: NO.
 
@@ -1766,6 +1842,13 @@ MAN_OUTPUT             = man
 
 MAN_EXTENSION          = .3
 
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
 # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
 # will generate one additional man file for each entity documented in the real
 # man page(s). These additional files only source the real man page, but without
@@ -1779,7 +1862,7 @@ MAN_LINKS              = NO
 # Configuration options related to the XML output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
 # captures the structure of the code including all documentation.
 # The default value is: NO.
 
@@ -1793,19 +1876,7 @@ GENERATE_XML           = NO
 
 XML_OUTPUT             = xml
 
-# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a
-# validating XML parser to check the syntax of the XML files.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_SCHEMA             =
-
-# The XML_DTD tag can be used to specify a XML DTD, which can be used by a
-# validating XML parser to check the syntax of the XML files.
-# This tag requires that the tag GENERATE_XML is set to YES.
-
-XML_DTD                =
-
-# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
 # listings (including syntax highlighting and cross-referencing information) to
 # the XML output. Note that enabling this will significantly increase the size
 # of the XML output.
@@ -1818,7 +1889,7 @@ XML_PROGRAMLISTING     = YES
 # Configuration options related to the DOCBOOK output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
 # that can be used to generate PDF.
 # The default value is: NO.
 
@@ -1832,14 +1903,23 @@ GENERATE_DOCBOOK       = NO
 
 DOCBOOK_OUTPUT         = docbook
 
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
 #---------------------------------------------------------------------------
 # Configuration options for the AutoGen Definitions output
 #---------------------------------------------------------------------------
 
-# 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.
+# 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.
 # The default value is: NO.
 
 GENERATE_AUTOGEN_DEF   = NO
@@ -1848,7 +1928,7 @@ GENERATE_AUTOGEN_DEF   = NO
 # Configuration options related to the Perl module output
 #---------------------------------------------------------------------------
 
-# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
 # file that captures the structure of the code including all documentation.
 #
 # Note that this feature is still experimental and incomplete at the moment.
@@ -1856,7 +1936,7 @@ GENERATE_AUTOGEN_DEF   = NO
 
 GENERATE_PERLMOD       = NO
 
-# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
 # Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
 # output from the Perl module output.
 # The default value is: NO.
@@ -1864,9 +1944,9 @@ GENERATE_PERLMOD       = NO
 
 PERLMOD_LATEX          = NO
 
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
 # formatted so it can be parsed by a human reader. This is useful if you want to
-# understand what is going on. On the other hand, if this tag is set to NO the
+# understand what is going on. On the other hand, if this tag is set to NO, the
 # size of the Perl module output will be much smaller and Perl will parse it
 # just the same.
 # The default value is: YES.
@@ -1886,14 +1966,14 @@ PERLMOD_MAKEVAR_PREFIX =
 # Configuration options related to the preprocessor
 #---------------------------------------------------------------------------
 
-# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
 # C-preprocessor directives found in the sources and include files.
 # The default value is: YES.
 
 ENABLE_PREPROCESSING   = YES
 
-# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
-# in the source code. If set to NO only conditional compilation will be
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
 # performed. Macro expansion can be done in a controlled way by setting
 # EXPAND_ONLY_PREDEF to YES.
 # The default value is: NO.
@@ -1909,7 +1989,7 @@ MACRO_EXPANSION        = YES
 
 EXPAND_ONLY_PREDEF     = NO
 
-# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
 # INCLUDE_PATH will be searched if a #include is found.
 # The default value is: YES.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
@@ -1952,9 +2032,9 @@ PREDEFINED             = __GNUC__=4 \
 EXPAND_AS_DEFINED      =
 
 # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
-# remove all refrences to function-like macros that are alone on a line, have an
-# all uppercase name, and do not end with a semicolon. Such function macros are
-# typically used for boiler-plate code, and will confuse the parser if not
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
 # removed.
 # The default value is: YES.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
@@ -1974,7 +2054,7 @@ SKIP_FUNCTION_MACROS   = YES
 # where loc1 and loc2 can be relative or absolute paths or URLs. See the
 # section "Linking to external documentation" for more information about the use
 # of tag files.
-# Note: Each tag file must have an unique name (where the name does NOT include
+# Note: Each tag file must have a unique name (where the name does NOT include
 # the path). If a tag file is not located in the directory in which doxygen is
 # run, you must also specify the path to the tagfile here.
 
@@ -1986,20 +2066,21 @@ TAGFILES               =
 
 GENERATE_TAGFILE       =
 
-# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
-# class index. If set to NO only the inherited external classes will be listed.
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
 # The default value is: NO.
 
 ALLEXTERNALS           = NO
 
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
-# the modules index. If set to NO, only the current project's groups will be
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
 # listed.
 # The default value is: YES.
 
 EXTERNAL_GROUPS        = YES
 
-# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
 # the related pages index. If set to NO, only the current project's pages will
 # be listed.
 # The default value is: YES.
@@ -2016,7 +2097,7 @@ PERL_PATH              = /usr/bin/perl
 # Configuration options related to the dot tool
 #---------------------------------------------------------------------------
 
-# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
 # (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
 # NO turns the diagrams off. Note that this option also works with HAVE_DOT
 # disabled, but it is recommended to install and use dot, since it yields more
@@ -2041,7 +2122,7 @@ MSCGEN_PATH            =
 
 DIA_PATH               =
 
-# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# If set to YES the inheritance and collaboration graphs will hide inheritance
 # and usage relations if the target is undocumented or is not a class.
 # The default value is: YES.
 
@@ -2066,7 +2147,7 @@ HAVE_DOT               = NO
 
 DOT_NUM_THREADS        = 0
 
-# When you want a differently looking font n the dot files that doxygen
+# When you want a differently looking font in the dot files that doxygen
 # generates you can specify the font name using DOT_FONTNAME. You need to make
 # sure dot is able to find the font, which can be done by putting it in a
 # standard location or by setting the DOTFONTPATH environment variable or by
@@ -2114,7 +2195,7 @@ COLLABORATION_GRAPH    = YES
 
 GROUP_GRAPHS           = YES
 
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
 # collaboration diagrams in a style similar to the OMG's Unified Modeling
 # Language.
 # The default value is: NO.
@@ -2166,7 +2247,8 @@ INCLUDED_BY_GRAPH      = YES
 #
 # Note that enabling this option will significantly increase the time of a run.
 # So in most cases it will be better to enable call graphs for selected
-# functions only using the \callgraph command.
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
@@ -2177,7 +2259,8 @@ CALL_GRAPH             = NO
 #
 # Note that enabling this option will significantly increase the time of a run.
 # So in most cases it will be better to enable caller graphs for selected
-# functions only using the \callergraph command.
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
@@ -2200,11 +2283,15 @@ GRAPHICAL_HIERARCHY    = YES
 DIRECTORY_GRAPH        = YES
 
 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot.
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
 # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
 # to make the SVG files visible in IE 9+ (other browsers do not have this
 # requirement).
-# Possible values are: png, jpg, gif and svg.
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
 # The default value is: png.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
@@ -2247,6 +2334,19 @@ MSCFILE_DIRS           =
 
 DIAFILE_DIRS           =
 
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
 # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
 # that will be shown in the graph. If the number of nodes in a graph becomes
 # larger than this value, doxygen will truncate the graph, which is visualized
@@ -2283,7 +2383,7 @@ MAX_DOT_GRAPH_DEPTH    = 0
 
 DOT_TRANSPARENT        = NO
 
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
 # files in one run (i.e. multiple -o and -T options on the command line). This
 # makes dot run faster, but since only newer versions of dot (>1.8.10) support
 # this, this feature is disabled by default.
@@ -2300,7 +2400,7 @@ DOT_MULTI_TARGETS      = NO
 
 GENERATE_LEGEND        = YES
 
-# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
 # files that are used to generate the various graphs.
 # The default value is: YES.
 # This tag requires that the tag HAVE_DOT is set to YES.
diff --git a/INSTALL b/INSTALL
index 85b4fab1ff04219e136ea00fe6cfb2d1a5ea97ab..45676b7ea44d0bb5cfc854272bdcbd5cf7a2e3a0 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -1,5 +1,11 @@
 Any knowledge of how to work with mouse and icons is not required.
 
+Installing lopsub
+~~~~~~~~~~~~~~~~~
+       git clone git://git.tuebingen.mpg.de/lopsub
+       cd lopsub && make && sudo make install
+       (see http://people.tuebingen.mpg.de/maan/lopsub/)
+
 Installing osl
 ~~~~~~~~~~~~~~
        git clone git://git.tuebingen.mpg.de/osl
@@ -12,14 +18,12 @@ Installing paraslash from tarball
 
 Installing paraslash from git
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-       autoconf && autoheader && make && sudo make install
+       autoconf && autoheader && ./configure && make && sudo make install
 
 Example for cross-compiling
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
-       export CROSS_COMPILE='armv6j-hardfloat-linux-gnueabi-'
+       export CC='armv6j-hardfloat-linux-gnueabi-gcc'
        export PATH="/usr/cross/arm/bin:$PATH"
-       export CC=${CROSS_COMPILE}gcc
-
        export LDFLAGS='
                -L/usr/sysroot/arm/lib
                -L/usr/sysroot/arm/usr/lib
@@ -29,7 +33,7 @@ Example for cross-compiling
        autoconf
        autoheader
        ./configure --host=arm-linux-gnueabi --prefix /usr/sysroot/arm/usr/local
-       make CROSS_COMPILE=$CROSS_COMPILE
+       make
 
 For details see the user manual:
 
index ec55c8e388a78a4fb88164aa0030320738e62c6e..d4a83a776eec0dfd602aa4329a72718cc15ea7f4 100644 (file)
@@ -8,17 +8,12 @@ datarootdir := @datarootdir@
 PACKAGE_TARNAME := @PACKAGE_TARNAME@
 PACKAGE_VERSION := @PACKAGE_VERSION@
 
-INSTALL := @INSTALL@
+FLEX := @FLEX@
+BISON := @BISON@
 M4 := @M4@
-GENGETOPT := @GENGETOPT@
-HELP2MAN := @HELP2MAN@
-
-ggo_descriptions_declared := @ggo_descriptions_declared@
+LOPSUBGEN := @LOPSUBGEN@
 
 executables := @executables@
-receivers := @receivers@
-filters := @filters@
-writers := @writers@
 
 recv_objs := @recv_objs@
 filter_objs := @filter_objs@
@@ -26,7 +21,7 @@ client_objs := @client_objs@
 gui_objs := @gui_objs@
 audiod_objs := @audiod_objs@
 audioc_objs := @audioc_objs@
-fade_objs := @fade_objs@
+mixer_objs := @mixer_objs@
 server_objs := @server_objs@
 write_objs := @write_objs@
 afh_objs := @afh_objs@
@@ -39,6 +34,7 @@ osl_cppflags := @osl_cppflags@
 id3tag_cppflags := @id3tag_cppflags@
 openssl_cppflags := @openssl_cppflags@
 gcrypt_cppflags := @gcrypt_cppflags@
+lopsub_cppflags := @lopsub_cppflags@
 ogg_cppflags := @ogg_cppflags@
 mad_cppflags := @mad_cppflags@
 faad_cppflags := @faad_cppflags@
@@ -50,9 +46,7 @@ samplerate_cppflags := @samplerate_cppflags@
 readline_cppflags := @readline_cppflags@
 alsa_cppflags := @alsa_cppflags@
 oss_cppflags := @oss_cppflags@
-mp4v2_cppflags := @mp4v2_cppflags@
 
-clock_gettime_ldflags := @clock_gettime_ldflags@
 id3tag_ldflags := @id3tag_ldflags@
 ogg_ldflags := @ogg_ldflags@
 vorbis_ldflags := @vorbis_ldflags@
@@ -61,6 +55,7 @@ opus_ldflags := @opus_ldflags@
 faad_ldflags := @faad_ldflags@
 mad_ldflags := @mad_ldflags@
 flac_ldflags := @flac_ldflags@
+lopsub_ldflags := @lopsub_ldflags@
 oss_ldflags := @oss_ldflags@
 alsa_ldflags := @alsa_ldflags@
 pthread_ldflags := @pthread_ldflags@
@@ -69,9 +64,7 @@ readline_ldflags := @readline_ldflags@
 samplerate_ldflags := @samplerate_ldflags@
 osl_ldflags := @osl_ldflags@
 curses_ldflags := @curses_ldflags@
-core_audio_ldflags := @core_audio_ldflags@
 crypto_ldflags := @crypto_ldflags@
 iconv_ldflags := @iconv_ldflags@
-mp4v2_ldflags := @mp4v2_ldflags@
 
 include Makefile.real
index 8ededf6aff8300608a5939429779ecd7b1753626..b88de22533048979bcfef8f3396b7608297afb3b 100644 (file)
@@ -6,10 +6,12 @@ MAKEFLAGS += -Rr
 ifeq ("$(origin CC)", "default")
         CC := cc
 endif
+.ONESHELL:
+.SHELLFLAGS := -ec
 
+LOGLEVELS := LL_DEBUG,LL_INFO,LL_NOTICE,LL_WARNING,LL_ERROR,LL_CRIT,LL_EMERG
 vardir := /var/paraslash
 mandir := $(datarootdir)/man/man1
-STRIP := $(CROSS_COMPILE)strip
 MKDIR_P := mkdir -p
 prefixed_executables := $(addprefix para_, $(executables))
 
@@ -18,30 +20,53 @@ 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 := 2017
+COPYRIGHT_YEAR := 2021
 
 ifeq ("$(origin O)", "command line")
        build_dir := $(O)
 else
        build_dir := build
 endif
-ggo_dir := $(build_dir)/ggo
 object_dir := $(build_dir)/objects
 dep_dir := $(build_dir)/deps
 man_dir := $(build_dir)/man/man1
-cmdline_dir := $(build_dir)/cmdline
-cmdlist_dir := $(build_dir)/cmdlist
 m4depdir := $(build_dir)/m4deps
-help2man_dir := $(build_dir)/help2man
-m4_ggo_dir := m4/gengetopt
+lls_suite_dir := $(build_dir)/lls
+lls_m4_dir := m4/lls
 test_dir := t
+yy_src_dir = yy
+yy_build_dir = $(build_dir)/yy
 
 # sort removes duplicate words, which is all we need here
 all_objs := $(sort $(recv_objs) $(filter_objs) $(client_objs) $(gui_objs) \
-       $(audiod_objs) $(audioc_objs) $(fade_objs) $(server_objs) \
+       $(audiod_objs) $(audioc_objs) $(mixer_objs) $(server_objs) \
        $(write_objs) $(afh_objs) $(play_objs))
-deps := $(addprefix $(dep_dir)/, $(filter-out %.cmdline.d, $(all_objs:.o=.d)))
-m4_deps := $(addprefix $(m4depdir)/, $(addsuffix .m4d, $(executables)))
+deps := $(addprefix $(dep_dir)/, $(all_objs:.o=.d))
+deps += $(addprefix $(dep_dir)/, mp.bison.d mp.flex.d)
+
+afh_objs += afh.lsg.o
+audioc_objs += audioc.lsg.o
+audiod_objs += $(addsuffix _cmd.lsg.o, recv filter audiod write) \
+       client.lsg.o audiod.lsg.o
+client_objs += client.lsg.o
+mixer_objs += mixer.lsg.o
+filter_objs += filter_cmd.lsg.o filter.lsg.o
+gui_objs += gui.lsg.o
+play_objs += $(addsuffix _cmd.lsg.o, recv filter play write) play.lsg.o
+recv_objs += recv_cmd.lsg.o recv.lsg.o
+server_objs += server_cmd.lsg.o server.lsg.o
+write_objs += write_cmd.lsg.o write.lsg.o
+
+cmd_suites := $(addsuffix _cmd, audiod server play recv filter write)
+suites := $(addprefix $(lls_suite_dir)/, $(cmd_suites) $(executables))
+m4_lls_deps := $(addsuffix .m4d, $(suites))
+lsg_h := $(addsuffix .lsg.h, $(suites))
+
+# flex/bison objects and headers are only needed if para_server is built
+ifeq ("$(findstring server, $(executables))", "server")
+       server_objs += mp.flex.o mp.bison.o
+       yy_h := $(yy_build_dir)/mp.bison.h
+endif
 
 # now prefix all objects with object dir
 recv_objs := $(addprefix $(object_dir)/, $(recv_objs))
@@ -50,7 +75,7 @@ client_objs := $(addprefix $(object_dir)/, $(client_objs))
 gui_objs := $(addprefix $(object_dir)/, $(gui_objs))
 audiod_objs := $(addprefix $(object_dir)/, $(audiod_objs))
 audioc_objs := $(addprefix $(object_dir)/, $(audioc_objs))
-fade_objs := $(addprefix $(object_dir)/, $(fade_objs))
+mixer_objs := $(addprefix $(object_dir)/, $(mixer_objs))
 server_objs := $(addprefix $(object_dir)/, $(server_objs))
 write_objs := $(addprefix $(object_dir)/, $(write_objs))
 afh_objs := $(addprefix $(object_dir)/, $(afh_objs))
@@ -61,59 +86,48 @@ man_pages := $(patsubst %, $(man_dir)/%.1, $(prefixed_executables))
 autocrap := config.h.in configure
 tarball_pfx := $(PACKAGE_TARNAME)-$(GIT_VERSION)
 tarball_delete := $(addprefix $(tarball_pfx)/, web .gitignore)
-tarball := $(tarball_pfx).tar.bz2
+tarball := $(tarball_pfx).tar.xz
 
-.PHONY: all clean clean2 distclean maintainer-clean install man tarball
 all: $(prefixed_executables) $(man_pages)
+.PHONY: all mostlyclean clean distclean maintainer-clean install \
+       install-strip man dist tarball
+
 man: $(man_pages)
-tarball: $(tarball)
 
-include $(m4_ggo_dir)/makefile
+include $(lls_m4_dir)/makefile
 include $(test_dir)/makefile.test
+include $(yy_src_dir)/makefile
 ifeq ($(findstring clean, $(MAKECMDGOALS)),)
 -include $(deps)
--include $(m4_deps)
+-include $(m4_lls_deps)
 endif
 
-$(object_dir) $(man_dir) $(ggo_dir) $(cmdline_dir) $(dep_dir) $(m4depdir) \
-               $(help2man_dir) $(cmdlist_dir):
-       $(Q) $(MKDIR_P) $@
-
-# When in doubt, use brute force (Ken Thompson)
-TOUPPER = \
-$(subst a,A,$(subst b,B,$(subst c,C,$(subst d,D,$(subst e,E,\
-$(subst f,F,$(subst g,G,$(subst h,H,$(subst i,I,$(subst j,J,\
-$(subst k,K,$(subst l,L,$(subst m,M,$(subst n,N,$(subst o,O,\
-$(subst p,P,$(subst q,Q,$(subst r,R,$(subst s,S,$(subst t,T,\
-$(subst u,U,$(subst v,V,$(subst w,W,$(subst x,X,$(subst y,Y,\
-$(subst z,Z,$1))))))))))))))))))))))))))
+$(object_dir) $(man_dir) $(dep_dir) $(m4depdir) $(lls_suite_dir) \
+       $(yy_build_dir):
+       @$(MKDIR_P) $@
 
 CPPFLAGS += -DBINDIR='"$(bindir)"'
 CPPFLAGS += -DCOPYRIGHT_YEAR='"$(COPYRIGHT_YEAR)"'
 CPPFLAGS += -DBUILD_DATE='"$(build_date)"'
+CPPFLAGS += -DLOGLEVELS='$(LOGLEVELS)'
 CPPFLAGS += -DUNAME_RS='"$(uname_rs)"'
 CPPFLAGS += -DCC_VERSION='"$(cc_version)"'
-CPPFLAGS += -I/usr/local/include
-CPPFLAGS += -I$(cmdline_dir)
-CPPFLAGS += -I$(cmdlist_dir)
-
-CFLAGS += -Os
-CFLAGS += -Wuninitialized
-CFLAGS += -Wchar-subscripts
-CFLAGS += -Werror-implicit-function-declaration
-CFLAGS += -Wmissing-noreturn
-CFLAGS += -Wbad-function-cast
-CFLAGS += -fno-strict-aliasing
-
-STRICT_CFLAGS = $(CFLAGS)
-STRICT_CFLAGS += -g -Wundef -W
+CPPFLAGS += -I$(lls_suite_dir)
+CPPFLAGS += -I$(yy_build_dir)
+CPPFLAGS += $(lopsub_cppflags)
+
+STRICT_CFLAGS += -fno-strict-aliasing
+STRICT_CFLAGS += -g
+STRICT_CFLAGS += -Os
+STRICT_CFLAGS += -Wundef -W -Wuninitialized
+STRICT_CFLAGS += -Wchar-subscripts
+STRICT_CFLAGS += -Werror-implicit-function-declaration
+STRICT_CFLAGS += -Wmissing-noreturn
+STRICT_CFLAGS += -Wbad-function-cast
 STRICT_CFLAGS += -Wredundant-decls
 STRICT_CFLAGS += -Wno-sign-compare -Wno-unknown-pragmas
-STRICT_CFLAGS += -Wformat -Wformat-security
-STRICT_CFLAGS += -Wmissing-format-attribute
 STRICT_CFLAGS += -Wdeclaration-after-statement
-
-LDFLAGS += $(clock_gettime_ldflags)
+STRICT_CFLAGS += -Wformat -Wformat-security -Wmissing-format-attribute
 
 ifeq ($(uname_s),Linux)
        # these cause warnings on *BSD
@@ -131,158 +145,117 @@ cc-option = $(shell \
 )
 
 STRICT_CFLAGS += $(call cc-option, -Wformat-signedness)
+STRICT_CFLAGS += $(call cc-option, -Wdiscarded-qualifiers)
 
 # To put more focus on warnings, be less verbose as default
 # Use 'make V=1' to see the full commands
 ifeq ("$(origin V)", "command line")
-       Q :=
+       SAY =
 else
-       Q := @
+       SAY = @echo '$(strip $(1))'
 endif
 
-$(cmdlist_dir)/%.command_list.h: %.cmd %.c | $(cmdlist_dir)
-       @[ -z "$(Q)" ] || echo 'GEN $@'
-       $(Q) ./command_util.bash h < $< >$@
-$(cmdlist_dir)/%.command_list.man: %.cmd %.c | $(cmdlist_dir)
-       @[ -z "$(Q)" ] || echo 'GEN $@'
-       $(Q) ./command_util.bash man < $< > $@
-$(cmdlist_dir)/%.completion.h: %.cmd | $(cmdlist_dir)
-       @[ -z "$(Q)" ] || echo 'GEN $@'
-       $(Q) ./command_util.bash compl $(strip $(call TOUPPER,$(*F)))_COMPLETERS \
-               $(strip $(call TOUPPER,$(*F)))_COMMANDS < $< > $@
-
-$(cmdlist_dir)/server.command_list.h \
-$(cmdlist_dir)/server.command_list.man \
-$(cmdlist_dir)/server.completion.h \
-: command.c
-
-$(cmdlist_dir)/afs.command_list.h \
-$(cmdlist_dir)/afs.command_list.man \
-$(cmdlist_dir)/afs.completion.h \
-: afs.c aft.c attribute.c
-
-$(cmdlist_dir)/audiod.command_list.h \
-$(cmdlist_dir)/audiod.command_list.man \
-$(cmdlist_dir)/audiod.completion.h \
-: audiod_command.c
-
-server_command_lists := $(cmdlist_dir)/server.command_list.man \
-       $(cmdlist_dir)/afs.command_list.man
-audiod_command_lists := $(cmdlist_dir)/audiod.command_list.man
-play_command_lists := $(cmdlist_dir)/play.command_list.man
-
-$(man_dir)/para_server.1: $(server_command_lists)
-$(man_dir)/para_audiod.1: $(audiod_command_lists)
-$(man_dir)/para_play.1: $(play_command_lists)
-
-$(man_dir)/para_server.1: man_util_command_lists := $(server_command_lists)
-$(man_dir)/para_audiod.1: man_util_command_lists := $(audiod_command_lists)
-$(man_dir)/para_play.1: man_util_command_lists := $(play_command_lists)
-
-$(man_dir)/para_%.1: $(ggo_dir)/%.ggo man_util.bash \
-               git-version.h | $(man_dir) $(help2man_dir)
-       @[ -z "$(Q)" ] || echo 'MAN $<'
-       $(Q) \
-               COMMAND_LISTS="$(man_util_command_lists)" \
-               FILTERS="$(filters)" \
-               GENGETOPT=$(GENGETOPT) \
-               GGO_DIR=$(ggo_dir) \
-               HELP2MAN=$(HELP2MAN) \
-               HELP2MAN_DIR=$(help2man_dir) \
-               RECEIVERS="$(receivers)" \
-               VERSION="$(GIT_VERSION)" \
-               WRITERS="$(writers)" \
-               ./man_util.bash $@
+audiod_commands := $(addprefix $(lls_suite_dir)/, \
+       $(addsuffix _cmd.lsg.man, audiod recv filter write))
+filter_commands := $(lls_suite_dir)/filter_cmd.lsg.man
+play_commands := $(lls_suite_dir)/play_cmd.lsg.man
+recv_commands := $(lls_suite_dir)/recv_cmd.lsg.man
+server_commands := $(lls_suite_dir)/server_cmd.lsg.man
+write_commands := $(lls_suite_dir)/write_cmd.lsg.man
+
+$(man_dir)/para_audiod.1: $(audiod_commands)
+$(man_dir)/para_filter.1: $(filter_commands)
+$(man_dir)/para_play.1: $(play_commands)
+$(man_dir)/para_recv.1: $(recv_commands)
+$(man_dir)/para_server.1: $(server_commands)
+$(man_dir)/para_write.1: $(write_commands)
+
+$(man_dir)/para_audiod.1: all_commands := $(audiod_commands)
+$(man_dir)/para_filter.1: all_commands := $(filter_commands)
+$(man_dir)/para_play.1: all_commands := $(play_commands)
+$(man_dir)/para_recv.1: all_commands := $(recv_commands)
+$(man_dir)/para_server.1: all_commands := $(server_commands)
+$(man_dir)/para_write.1: all_commands := $(write_commands)
+
+$(man_dir)/para_%.1: $(lls_suite_dir)/%.lsg.man \
+               $(lls_m4_dir)/copyright.m4 | $(man_dir)
+       $(call SAY, LLSMAN $<)
+       cat $< $(all_commands) > $@
+       $(M4) -D COPYRIGHT_YEAR=$(COPYRIGHT_YEAR) $(lls_m4_dir)/copyright.m4 >> $@
 
 $(object_dir)/%.o: %.c | $(object_dir)
 
-$(object_dir)/opus%.o $(dep_dir)/opus%.d: CPPFLAGS += $(opus_cppflags)
-$(object_dir)/gui.o $(object_dir)/gui%.o $(dep_dir)/gui%.d \
+$(object_dir)/opus%.o: CPPFLAGS += $(opus_cppflags)
+$(object_dir)/gui.o $(object_dir)/gui%.o \
 : CPPFLAGS += $(curses_cppflags)
-$(object_dir)/spx%.o $(dep_dir)/spx%.d: CPPFLAGS += $(speex_cppflags)
-$(object_dir)/flac%.o $(dep_dir)/flac%.d: CPPFLAGS += $(flac_cppflags)
+$(object_dir)/spx%.o: CPPFLAGS += $(speex_cppflags)
+$(object_dir)/flac%.o: CPPFLAGS += $(flac_cppflags)
 
-$(object_dir)/mp3_afh.o $(dep_dir)/mp3_afh.d: CPPFLAGS += $(id3tag_cppflags)
-$(object_dir)/crypt.o $(dep_dir)/crypt.d: CPPFLAGS += $(openssl_cppflags)
-$(object_dir)/gcrypt.o $(dep_dir)/gcrypt.d: CPPFLAGS += $(gcrypt_cppflags)
-$(object_dir)/ao_write.o $(dep_dir)/ao_write.d: CPPFLAGS += $(ao_cppflags)
-$(object_dir)/aac_afh.o $(dep_dir)/aac_afh.d: CPPFLAGS += $(mp4v2_cppflags)
-$(object_dir)/alsa%.o $(dep_dir)/alsa%.d: CPPFLAGS += $(alsa_cppflags)
+$(object_dir)/mp3_afh.o: CPPFLAGS += $(id3tag_cppflags)
+$(object_dir)/openssl.o: CPPFLAGS += $(openssl_cppflags)
+$(object_dir)/gcrypt.o: CPPFLAGS += $(gcrypt_cppflags)
+$(object_dir)/ao_write.o: CPPFLAGS += $(ao_cppflags)
+$(object_dir)/alsa%.o: CPPFLAGS += $(alsa_cppflags)
 
-$(object_dir)/interactive.o $(dep_dir)/interactive.d \
+$(object_dir)/interactive.o \
 : CPPFLAGS += $(readline_cppflags)
 
-$(object_dir)/resample_filter.o $(dep_dir)/resample_filter.d \
+$(object_dir)/resample_filter.o \
 : CPPFLAGS += $(samplerate_cppflags)
 
-$(object_dir)/oss_write.o $(dep_dir)/oss_write.d \
+$(object_dir)/oss_write.o \
 : CPPFLAGS += $(oss_cppflags)
 
-$(object_dir)/ao_write.o $(dep_dir)/ao_write.d \
+$(object_dir)/ao_write.o \
 : CPPFLAGS += $(ao_cppflags) $(pthread_cppflags)
 
-$(object_dir)/mp3dec_filter.o $(dep_dir)/mp3dec_filter.d \
+$(object_dir)/mp3dec_filter.o \
 : CPPFLAGS += $(mad_cppflags)
 
-$(object_dir)/aacdec_filter.o $(dep_dir)/aacdec_filter.d \
-$(object_dir)/aac_common.o $(dep_dir)/aac_common.d \
-$(object_dir)/aac_afh.o $(dep_dir)/aac_afh.d \
+$(object_dir)/aacdec_filter.o \
+$(object_dir)/aac_afh.o \
 : CPPFLAGS += $(faad_cppflags)
 
-$(object_dir)/ogg_afh.o $(dep_dir)/ogg_afh.d \
-$(object_dir)/oggdec_filter.o $(dep_dir)/oggdec_filter.d \
+$(object_dir)/ogg_afh.o \
+$(object_dir)/oggdec_filter.o \
 : CPPFLAGS += $(vorbis_cppflags)
 
-$(object_dir)/spx_common.o $(dep_dir)/spx_common.d \
-$(object_dir)/spxdec_filter.o $(dep_dir)/spxdec_filter.d \
-$(object_dir)/spx_afh.o $(dep_dir)/spx_afh.d \
-$(object_dir)/oggdec_filter.o $(dep_dir)/oggdec_filter.d \
-$(object_dir)/ogg_afh.o $(dep_dir)/ogg_afh.d \
-$(object_dir)/ogg_afh_common.o $(dep_dir)/ogg_afh_common.d \
-$(object_dir)/opus%.o $(dep_dir)/opus%.d \
+$(object_dir)/spx_common.o \
+$(object_dir)/spxdec_filter.o \
+$(object_dir)/spx_afh.o \
+$(object_dir)/oggdec_filter.o \
+$(object_dir)/ogg_afh.o \
+$(object_dir)/ogg_afh_common.o \
+$(object_dir)/opus%.o \
 : CPPFLAGS += $(ogg_cppflags)
 
-$(object_dir)/afs.o $(dep_dir)/afs.d \
-$(object_dir)/aft.o $(dep_dir)/aft.d \
-$(object_dir)/attribute.o $(dep_dir)/attribute.d \
-$(object_dir)/blob.o $(dep_dir)/blob.d  \
-$(object_dir)/mood.o $(dep_dir)/mood.d \
-$(object_dir)/playlist.o $(dep_dir)/playlist.d \
-$(object_dir)/score.o $(dep_dir)/score.d \
-$(object_dir)/server.o $(dep_dir)/server.d \
-$(object_dir)/vss.o $(dep_dir)/vss.d \
-$(object_dir)/command.o $(dep_dir)/command.d \
-$(object_dir)/http_send.o $(dep_dir)/http_send.d \
-$(object_dir)/dccp_send.o $(dep_dir)/dccp_send.d \
-$(object_dir)/udp_send.o $(dep_dir)/udp_send.d \
-$(object_dir)/send_common.o $(dep_dir)/send_common.d \
-$(object_dir)/mm.o $(dep_dir)/mm.d \
+$(object_dir)/afs.o \
+$(object_dir)/aft.o \
+$(object_dir)/attribute.o \
+$(object_dir)/blob.o  \
+$(object_dir)/mood.o \
+$(object_dir)/playlist.o \
+$(object_dir)/score.o \
+$(object_dir)/server.o \
+$(object_dir)/vss.o \
+$(object_dir)/command.o \
+$(object_dir)/http_send.o \
+$(object_dir)/dccp_send.o \
+$(object_dir)/udp_send.o \
+$(object_dir)/send_common.o \
+$(object_dir)/mm.o \
 : CPPFLAGS += $(osl_cppflags)
 
-$(object_dir)/%.cmdline.o: CFLAGS += -Wno-unused-function
 $(object_dir)/compress_filter.o: CFLAGS += -O3
 
-$(object_dir)/%.o: %.c | $(object_dir)
-       @[ -z "$(Q)" ] || echo 'CC $<'
-       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(STRICT_CFLAGS) $<
-
-$(object_dir)/%.cmdline.o: $(cmdline_dir)/%.cmdline.c $(cmdline_dir)/%.cmdline.h | $(object_dir)
-       @[ -z "$(Q)" ] || echo 'CC $<'
-       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $<
-
-# The compiler outputs dependencies either as foo.h or as some_directory/foo.h,
-# depending on whether the latter file exists. Since make needs the directory
-# part we prefix the dependency as appropriate.
-$(dep_dir)/%.d: %.c | $(dep_dir)
-       @[ -z "$(Q)" ] || echo 'DEP $<'
-       $(Q) $(CC) $(CPPFLAGS) -MM -MG -MP -MT $@ -MT $(object_dir)/$(*F).o $< \
-               | sed -e "s@ \([a-zA-Z0-9_]\{1,\}\.cmdline.h\)@ $(cmdline_dir)/\1@g" \
-               -e "s@ \([a-zA-Z0-9_]\{1,\}\.command_list.h\)@ $(cmdlist_dir)/\1@g" \
-               -e "s@ \([a-zA-Z0-9_]\{1,\}\.completion.h\)@ $(cmdlist_dir)/\1@g" > $@
+$(object_dir)/%.o: %.c | $(object_dir) $(dep_dir) $(lsg_h) $(yy_h)
+       $(call SAY, CC $<)
+       $(CC) -c -o $@ -MMD -MF $(dep_dir)/$(*F).d -MT $@ $(CPPFLAGS) \
+               $(STRICT_CFLAGS) $(CFLAGS) $<
 
 para_recv para_afh para_play para_server: LDFLAGS += $(id3tag_ldflags)
 para_write para_play para_audiod \
-: LDFLAGS += $(ao_ldflags) $(pthread_ldflags) $(core_audio_ldflags)
+: LDFLAGS += $(ao_ldflags) $(pthread_ldflags)
 para_client para_audioc para_play : LDFLAGS += $(readline_ldflags)
 para_server: LDFLAGS += $(osl_ldflags)
 para_gui: LDFLAGS += $(curses_ldflags)
@@ -301,14 +274,29 @@ para_play \
        $(samplerate_ldflags) \
        -lm
 
+para_mixer: LDFLAGS += -lm
+
 para_write \
 para_play \
 para_audiod \
-para_fade \
+para_mixer \
 : LDFLAGS += \
        $(oss_ldflags) \
        $(alsa_ldflags)
 
+para_afh \
+para_audioc \
+para_audiod \
+para_client \
+para_mixer \
+para_filter \
+para_gui \
+para_play \
+para_recv \
+para_server \
+para_write \
+: LDFLAGS += $(lopsub_ldflags)
+
 para_server \
 para_filter \
 para_audiod \
@@ -323,53 +311,52 @@ para_recv \
        $(faad_ldflags) \
        $(flac_ldflags)
 
-para_server \
-para_play \
-para_afh \
-para_recv \
-: LDFLAGS += \
-       $(mp4v2_ldflags)
-
 para_afh para_recv para_server para_play: LDFLAGS += $(iconv_ldflags)
 
 $(foreach exe,$(executables),$(eval para_$(exe): $$($(exe)_objs)))
 $(prefixed_executables):
-       @[ -z "$(Q)" ] || echo 'LD $@'
-       $(Q) $(CC) $^ -o $@ $(LDFLAGS)
-
-clean:
-       @[ -z "$(Q)" ] || echo 'CLEAN'
-       $(Q) rm -f para_*
-       $(Q) rm -rf $(object_dir)
-
-clean2: clean
-       @[ -z "$(Q)" ] || echo 'CLEAN2'
-       $(Q) rm -rf $(build_dir)
-distclean: clean2 test-clean
-       @[ -z "$(Q)" ] || echo 'DISTCLEAN'
-       $(Q) rm -f Makefile autoscan.log config.status config.log
-       $(Q) rm -f GPATH GRTAGS GSYMS GTAGS
-
+       $(call SAY, LD $@)
+       $(CC) $^ -o $@ $(LDFLAGS)
+
+mostlyclean:
+       $(call SAY, MOSTLYCLEAN)
+       rm -f para_*
+       rm -rf $(object_dir)
+clean: mostlyclean
+       $(call SAY, CLEAN)
+       rm -rf $(build_dir)
+distclean: clean
+       $(call SAY, DISTCLEAN)
+       rm -f Makefile autoscan.log config.status config.log
+       rm -f config.h configure config.h.in
 maintainer-clean: distclean
-       @[ -z "$(Q)" ] || echo 'MAINTAINER-CLEAN'
-       $(Q) rm -f *.tar.bz2 config.h configure config.h.in
-
-install: all man
-       $(MKDIR_P) $(bindir) $(mandir)
-       $(INSTALL) -s --strip-program $(STRIP) -m 755 \
-               $(prefixed_executables) $(bindir)
-       $(INSTALL) -m 644 $(man_pages) $(mandir)
-       $(MKDIR_P) $(vardir) >/dev/null 2>&1 || true # not fatal, so don't complain
-
-$(tarball):
-       $(Q) rm -rf $(tarball) $(tarball_pfx)
-       $(Q) git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \
-               | tar --delete $(tarball_delete) > $(tarball_pfx).tar
-       $(Q) $(MKDIR_P) $(tarball_pfx)
-       $(Q) ./GIT-VERSION-GEN > $(tarball_pfx)/VERSION
-       $(Q) cp $(autocrap) $(tarball_pfx)
-       $(Q) tar rf $(tarball_pfx).tar $(tarball_pfx)/*
-       $(Q) bzip2 -9 $(tarball_pfx).tar
-       $(Q) ls -l $(tarball)
-       $(Q) ln -sf $(tarball) paraslash-git.tar.bz2
-       $(Q) rm -rf $(tarball_pfx)
+       $(call SAY, MAINTAINER-CLEAN)
+       rm -f *.tar.bz2 *.tar.xz
+       rm -f GPATH GRTAGS GSYMS GTAGS
+
+INSTALL ?= install
+INSTALL_PROGRAM ?= $(INSTALL)
+INSTALL_DATA ?= $(INSTALL) -m 644
+ifneq ($(findstring strip, $(MAKECMDGOALS)),)
+       strip_option := -s
+endif
+
+install install-strip: all man
+       $(MKDIR_P) $(DESTDIR)$(bindir) $(DESTDIR)$(mandir)
+       $(INSTALL) $(strip_option) $(prefixed_executables) $(DESTDIR)$(bindir)
+       $(INSTALL_DATA) $(man_pages) $(DESTDIR)$(mandir)
+       $(MKDIR_P) $(DESTDIR)$(vardir) >/dev/null 2>&1 || true # not fatal, so don't complain
+
+$(tarball) dist tarball:
+       $(call SAY, DIST)
+       rm -rf $(tarball) $(tarball_pfx)
+       git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \
+          | tar --delete $(tarball_delete) > $(tarball_pfx).tar
+       $(MKDIR_P) $(tarball_pfx)
+       ./GIT-VERSION-GEN > $(tarball_pfx)/VERSION
+       cp $(autocrap) $(tarball_pfx)
+       tar rf $(tarball_pfx).tar $(tarball_pfx)/*
+       xz -9 $(tarball_pfx).tar
+       ls -l $(tarball)
+       ln -sf $(tarball) paraslash-git.tar.xz
+       rm -rf $(tarball_pfx)
diff --git a/NEWS.md b/NEWS.md
index d6b0abc758fafb785b0ee82be086c255eb022f92..7dc331f516ba15b9443daf69cd1ba19b2202c21e 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -18,9 +18,118 @@ Downloads:
 [tarball](./releases/paraslash-0.5.9.tar.bz2),
 [signature](./releases/paraslash-0.5.9.tar.bz2.asc)
 
+----------------------------------------
+0.6.3 (2021-02-18) "generalized activity"
+-----------------------------------------
+
+It has been a while since the last release, but paraslash-0.6.3
+finally has arrived. It contains a fair number of improvements,
+features and fixes all over the place.
+
+- The ff command now accepts a negative argument to instruct the
+  virtual streaming system to jump backwards in the current audio
+  stream. The old syntax (e.g., "ff 30-") is still supported but it
+  is deprecated and no longer documented. The compatibility code is
+  sheduled for removal after 0.7.0.
+- para_afh: New option: --preserve to reset the modification time to
+  the value of the original file after meta data modification.
+- Overhaul of the compress filter code. The refined algorithm should
+  reduce clipping. The meaning of --aggressiveness has changed, see the
+  updated and extended documentation of the compress filter for details.
+- Cleanup of the audio format handler code.
+- We now build the tree using the .ONESHELL feature of GNU make,
+  which results in a significant speedup.
+- Two robustness fixes for FreeBSD.
+- para_client now supports RFC4716 private keys as generated with
+  ssh-keygen -m RFC4716. In fact, this key format has been made the
+  default, and the former PEM keys will be depreciated at some point.
+- The ogg audio format handlers learned to detect holes and now report
+  the correct duration also if ogg pages are missing in the file. This
+  affects ogg/vorbis ogg/speex and ogg/opus.
+- Robustness improvements for para_mixer.
+- A fix for an old bug that could cause the server to crash or report
+  garbage in its status output.
+- New para_play option: --end-of-playlist
+- Streaming m4a files over udp has been improved.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.3.tar.xz),
+[signature](./releases/paraslash-0.6.3.tar.xz.asc)
+
+--------------------------------------
+0.6.2 (2018-06-30) "elastic diversity"
+--------------------------------------
+
+The main new feature of this release is the --listen-address option
+for para_server and para_audiod. But there are also a large number of
+small improvements and bug fixes all over the place. The user-visible
+changes include
+
+- para_gui no longer waits up to one second to update the screen when
+  the geometry of the terminal changes.
+- Minor documentation improvements.
+- Improvements to the crypto subsystem.
+- The server subcommand "task" has been deprecated. It still works,
+  but prints nothing. It will be removed in the next major release.
+- Server log output is now serialized, avoiding issues with partial
+  lines.
+- It is now possible to switch to a different afs database by changing
+  the server configuration and sending SIGHUP to the server process.
+- New server options: --listen--address, --http-listen-address and
+  --dccp-listen-address. These options restrict the set of listening
+  addresses of the TCP and DCCP sockets of the server process.
+- The help commands of server, audiod, play now support --long. By default,
+  the short help is shown.
+- The code which merges the command line options with the config file
+  options has been consolidated.
+- If the current audio file is renamed, the status items are now updated
+  accordingly.
+- After the server process received SIGHUP, changes to the current audio
+  file did not trigger an update of the status items. This has been fixed.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.2.tar.xz),
+[signature](./releases/paraslash-0.6.2.tar.xz.asc)
+
+----------------------------------------
+0.6.1 (2017-09-23) "segmented iteration"
+----------------------------------------
+
+The highlight of this release is the version 2 mood parser. But there
+is a lot more than that, as summarized in the list below. And of
+course we have many small usability improvements and bug fixes not
+mentioned here.
+
+- A more intuitive syntax for moods ("version 2 moods"). The
+  traditional version 1 moods are still supported but are deprecated
+  now. Removal of the version 1 mood parser is scheduled for the next
+  major release.
+- Flex and bison are now required to build para_server.
+- New sort order for the ls command: -s=h sorts the ls output by hash
+  value of the audio file.
+- autogen.sh now runs the test suite after a successful build.
+- The contents of overview.pdf have been integrated into the user
+  manual.
+- Fixed sized audio format headers for ogg/opus streams.
+- The doxygen source browser has been disabled temporarily. The
+  API reference is still online, though.
+- Overhaul of the source code documentation.
+- The deprecated --full-path option of the ls command has been
+  removed. It was a no-op since 0.6.0.
+- The wma decoder has been cleaned up and its bitstream API made
+  more robust.
+- The image/lyrics ID status items of the current audio file are now
+  updated on changes. This affects para_gui, which used to report the
+  old value until EOF.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.1.tar.xz),
+[signature](./releases/paraslash-0.6.1.tar.xz.asc)
+
 ---------------------------------------
 0.5.8 (2017-09-23) "branching parabola"
 ---------------------------------------
+
 A couple of fixes for serious old bugs have accummulated in the maint
 branch since paraslash-0.5.7 was released nine months ago. So here
 is another 0.5.x release. As usual, all commits in the maint branch
@@ -31,6 +140,78 @@ Downloads:
 [tarball](./releases/paraslash-0.5.8.tar.bz2),
 [signature](./releases/paraslash-0.5.8.tar.bz2.asc)
 
+-------------------------------
+0.6.0 (2017-04-28) "fuzzy flux"
+-------------------------------
+
+The highlights of this release are the conversion of all option parsers
+to the lopsub library and the improved AAC support. But there are
+many other user-visible changes all over the place. Also a lot of
+old cruft has been removed, leading to incompatible changes. Hence
+the new major version number.
+
+- Support for Mac OS X has been removed.
+- On Linux systems, glibc-2.17 or newer is required to build the
+  source tree.
+- Support for RSA public keys in ASN format (as generated by openssl
+  genrsa) has been removed. These keys have been deprecated since
+  2011, so users should have long switched to keys generated with
+  ssh-keygen(1).
+- If libgcrypt is used as the crypto library, we now require version
+  1.5.0 (released in 2011) or later.
+- The insecure RC4 stream cipher has been removed. It was superseded
+  by aes_ctr128 three years ago but the RC4 code had been kept for
+  backwards compatibility.
+- On Linux, abstract unix domain sockets are used unconditionally.
+- The "install" target no longer strips executables, the new
+  install-strip target can be used to get the old behaviour.
+- The clean targets have been renamed: clean2 is gone, and the new
+  mostlyclean removes only the executables and object files.
+- New target: check (identical to test).
+- The DESTDIR make variable is honored to prepend a path to the
+  installation directory. This feature is orthogonal to the --prefix
+  option to configure.
+- Minor WMA cleanups.
+- The aac audio format handler has been rewritten to use the mp4ff library.
+  See the manual for how to install the library on your system.
+- New status item: max_chunk_size. The value is stored in a previously
+  unused field of the afhi object of the aft table. Although backwards
+  compatible, users are encouraged to re-add m4a files to populate
+  the new field.
+- No more chunk tables for aac. Chunk boundaries are determined
+  dynamically at stream time.
+- Release and master branch tarballs are now compressed with xz rather
+  than bzip2.
+- The lopsub package is required to install the paraslash package.
+  Gengetopt is no longer needed.
+- make dep is gone. Dependencies have been created automatically for
+  a long time and it was never necessary to run make dep manually.
+- para_gui lost its --timeout option.
+- Most manual pages have been extended to include an overall
+  description of the command.
+- The --stream-delay option of para_audiod has been removed. It had
+  been a no-op for many years.
+- The deprecated --path option of the server ls command has been
+  removed.  The command now prints full paths by default, making
+  --full-path a no-op. Hence --full-path has been depreacted and is
+  scheduled for removal in v0.6.1.
+- It is now possible to use 'CFLAGS' to override the default compiler
+  options.
+- para_fade has been renamed to para_mixer. The four modes of operation
+  (set, fade, snooze, sleep) are implemented as subcommands of the
+  new program.
+- The sleep subcommand of para_mixer (former sleep mode of para_fade)
+  lost its --wake-hour and --wake-min options in favor of the new
+  --wake-time option which also accepts relative wakeup times like
+  "+9:30".
+- The new --fade-exponent option of para_mixer allows for non-linear
+  channel fading.
+- The new logo.
+
+Downloads:
+[tarball](./releases/paraslash-0.6.0.tar.xz),
+[signature](./releases/paraslash-0.6.0.tar.xz.asc)
+
 -------------------------------------
 0.5.7 (2016-12-31) "semantic density"
 -------------------------------------
diff --git a/aac.h b/aac.h
deleted file mode 100644 (file)
index eeed252..0000000
--- a/aac.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file aac.h Exported symbols from aac_common.c. */
-
-#include <neaacdec.h>
-
-NeAACDecHandle aac_open(void);
-int aac_find_esds(char *buf, size_t buflen, size_t *skip,
-               unsigned long *decoder_length);
-ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip);
index 1c7fd706f0a7c9fe8c9d4d8db43846be18a9304d..2b3dd2cc538a57a233813b77adcb332bd77929ce 100644 (file)
--- a/aac_afh.c
+++ b/aac_afh.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 /*
  * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker,
  * Ahead Software AG
 /** \file aac_afh.c para_server's aac audio format handler. */
 
 #include <regex.h>
-#include <mp4v2/mp4v2.h>
+#include <neaacdec.h>
 
 #include "para.h"
+
+/* To get the mp4ff_tag_t and mp4ff_metadata_t typedefs. */
+#define USE_TAGGING
+#include <mp4ff.h>
+
 #include "error.h"
 #include "portable_io.h"
 #include "afh.h"
 #include "string.h"
-#include "aac.h"
 #include "fd.h"
 
-static int aac_find_stsz(char *buf, size_t buflen, off_t *skip)
+
+struct aac_afh_context {
+       const void *map;
+       size_t mapsize;
+       size_t fpos;
+       int32_t track;
+       mp4ff_t *mp4ff;
+       mp4AudioSpecificConfig masc;
+       mp4ff_callback_t cb;
+};
+
+static uint32_t aac_afh_read_cb(void *user_data, void *dest, uint32_t want)
 {
-       int i;
-
-       for (i = 0; i + 16 < buflen; i++) {
-               char *p = buf + i;
-               unsigned sample_count, sample_size;
-
-               if (p[0] != 's' || p[1] != 't' || p[2] != 's' || p[3] != 'z')
-                       continue;
-               PARA_DEBUG_LOG("found stsz@%d\n", i);
-               i += 8;
-               sample_size = read_u32_be(buf + i);
-               PARA_DEBUG_LOG("sample size: %u\n", sample_size);
-               i += 4;
-               sample_count = read_u32_be(buf + i);
-               i += 4;
-               PARA_DEBUG_LOG("sample count: %u\n", sample_count);
-               *skip = i;
-               return sample_count;
+       struct aac_afh_context *c = user_data;
+       uint32_t have, rv;
+
+       if (want == 0 || c->fpos >= c->mapsize) {
+               PARA_INFO_LOG("failed attempt to read %u bytes @%zu\n", want,
+                       c->fpos);
+               errno = EAGAIN;
+               return -1;
        }
-       return -E_STSZ;
+       have = c->mapsize - c->fpos;
+       rv = PARA_MIN(have, want);
+       PARA_DEBUG_LOG("reading %u bytes @%zu\n", rv, c->fpos);
+       memcpy(dest, c->map + c->fpos, rv);
+       c->fpos += rv;
+       return rv;
 }
 
-static int atom_cmp(const char *buf1, const char *buf2)
+static uint32_t aac_afh_seek_cb(void *user_data, uint64_t pos)
 {
-       return memcmp(buf1, buf2, 4)? 1 : 0;
+       struct aac_afh_context *c = user_data;
+       c->fpos = pos;
+       return 0;
 }
 
-static int read_atom_header(char *buf, uint64_t *subsize, char type[5])
+static int32_t aac_afh_get_track(mp4ff_t *mp4ff, mp4AudioSpecificConfig *masc)
 {
-       uint64_t size = read_u32_be(buf);
-
-       memcpy(type, buf + 4, 4);
-       type[4] = '\0';
-
-       PARA_DEBUG_LOG("size: %llu, type: %s\n", (long long unsigned)size, type);
-       if (size != 1) {
-               *subsize = size;
-               return 8;
+       int32_t i, rc, num_tracks = mp4ff_total_tracks(mp4ff);
+
+       assert(num_tracks >= 0);
+       for (i = 0; i < num_tracks; i++) {
+               unsigned char *buf = NULL;
+               unsigned buf_size = 0;
+
+               mp4ff_get_decoder_config(mp4ff, i, &buf, &buf_size);
+               if (buf) {
+                       rc = NeAACDecAudioSpecificConfig(buf, buf_size, masc);
+                       free(buf);
+                       if (rc < 0)
+                               continue;
+                       return i;
+               }
        }
-       buf += 4;
-       size = 0;
-       size = read_u64_be(buf);
-       *subsize = size;
-       return 16;
+       return -1; /* no audio track */
 }
 
-static char *get_tag(char *p, int size)
+static int aac_afh_open(const void *map, size_t mapsize, void **afh_context)
 {
-       char *buf;
-
-       assert(size > 0);
-       buf = para_malloc(size + 1);
-
-       memcpy(buf, p, size);
-       buf[size] = '\0';
-       PARA_DEBUG_LOG("size: %d: %s\n", size, buf);
-       return buf;
+       int ret;
+       struct aac_afh_context *c = para_malloc(sizeof(*c));
+
+       c->map = map;
+       c->mapsize = mapsize;
+       c->fpos = 0;
+       c->cb.read = aac_afh_read_cb;
+       c->cb.seek = aac_afh_seek_cb;
+       c->cb.user_data = c;
+
+       ret = -E_MP4FF_OPEN;
+       c->mp4ff = mp4ff_open_read(&c->cb);
+       if (!c->mp4ff)
+               goto free_ctx;
+       c->track = aac_afh_get_track(c->mp4ff, &c->masc);
+       ret = -E_MP4FF_TRACK;
+       if (c->track < 0)
+               goto close_mp4ff;
+       *afh_context = c;
+       return 0;
+close_mp4ff:
+       mp4ff_close(c->mp4ff);
+free_ctx:
+       free(c);
+       *afh_context = NULL;
+       return ret;
 }
 
-static void read_tags(char *buf, size_t buflen, struct afh_info *afhi)
+static void aac_afh_close(void *afh_context)
 {
-       char *p = buf;
+       struct aac_afh_context *c = afh_context;
+       mp4ff_close(c->mp4ff);
+       free(c);
+}
 
-       while (p + 32 < buf + buflen) {
-               char *q, type1[5], type2[5];
-               uint64_t size1, size2;
-               int ret, ret2;
+/**
+ * Libmp4ff function to reposition the file to the given sample.
+ *
+ * \param f The opaque handle returned by mp4ff_open_read().
+ * \param track The number of the (audio) track.
+ * \param sample Destination.
+ *
+ * We need this function to obtain the offset of the sample within the audio
+ * file. Unfortunately, it is not exposed in the mp4ff header.
+ *
+ * \return This function always returns 0.
+ */
+int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample);
 
-               ret = read_atom_header(p, &size1, type1);
-               ret2 = read_atom_header(p + ret, &size2, type2);
+static int aac_afh_get_chunk(long unsigned chunk_num, void *afh_context,
+               const char **buf, size_t *len)
+{
+       struct aac_afh_context *c = afh_context;
+       int32_t ss;
+       size_t offset;
+
+       assert(chunk_num <= INT_MAX);
+       /* this function always returns zero */
+       mp4ff_set_sample_position(c->mp4ff, c->track, chunk_num);
+       offset = c->fpos;
+       ss = mp4ff_read_sample_getsize(c->mp4ff, c->track, chunk_num);
+       if (ss <= 0)
+               return -E_MP4FF_BAD_SAMPLE;
+       assert(ss + offset <= c->mapsize);
+       *buf = c->map + offset;
+       *len = ss;
+       return 1;
+}
 
-               if (size2 <= 16 || atom_cmp(type2, "data")) {
-                       p += size1;
-                       continue;
-               }
-               size2 -= 16;
-               q = p + ret + ret2 + 8;
-               if (q + size2 > buf + buflen)
-                       break;
-               if (!atom_cmp(type1, "\xa9" "ART"))
-                       afhi->tags.artist = get_tag(q, size2);
-               else if (!atom_cmp(type1, "\xa9" "alb"))
-                       afhi->tags.album = get_tag(q, size2);
-               else if (!atom_cmp(type1, "\xa9" "nam"))
-                       afhi->tags.title = get_tag(q, size2);
-               else if (!atom_cmp(type1, "\xa9" "cmt"))
-                       afhi->tags.comment = get_tag(q, size2);
-               else if (!atom_cmp(type1, "\xa9" "day"))
-                       afhi->tags.year = get_tag(q, size2);
-               p += size1;
-       }
+static void _aac_afh_get_taginfo(const mp4ff_t *mp4ff, struct taginfo *tags)
+{
+       mp4ff_meta_get_artist(mp4ff, &tags->artist);
+       mp4ff_meta_get_title(mp4ff, &tags->title);
+       mp4ff_meta_get_date(mp4ff, &tags->year);
+       mp4ff_meta_get_album(mp4ff, &tags->album);
+       mp4ff_meta_get_comment(mp4ff, &tags->comment);
 }
 
-static void read_meta(char *buf, size_t buflen, struct afh_info *afhi)
+/*
+ * Init m4a file and write some tech data to given pointers.
+ */
+static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
+               struct afh_info *afhi)
 {
-       char *p = buf;
+       int ret;
+       int32_t rv;
+       struct aac_afh_context *c;
+       int64_t tmp;
+       const char *buf;
+       size_t sz;
+       uint32_t n;
+
+       ret = aac_afh_open(map, numbytes, (void **)&c);
+       if (ret < 0)
+               return ret;
 
-       while (p + 4 < buf + buflen) {
+       ret = -E_MP4FF_BAD_SAMPLERATE;
+       rv = mp4ff_get_sample_rate(c->mp4ff, c->track);
+       if (rv <= 0)
+               goto close;
+       afhi->frequency = rv;
 
-               if (p[0] != 'i' || p[1] != 'l' || p[2] != 's' || p[3] != 't') {
-                       p++;
-                       continue;
-               }
-               p += 4;
-               return read_tags(p, buflen - (p - buf), afhi);
+       ret = -E_MP4FF_BAD_CHANNEL_COUNT;
+       rv = mp4ff_get_channel_count(c->mp4ff, c->track);
+       if (rv <= 0)
+               goto close;
+       afhi->channels = rv;
+
+       ret = -E_MP4FF_BAD_SAMPLE_COUNT;
+       rv = mp4ff_num_samples(c->mp4ff, c->track);
+       if (rv <= 0)
+               goto close;
+       afhi->chunks_total = rv;
+       afhi->max_chunk_size = 0;
+       for (n = 0; n < afhi->chunks_total; n++) {
+               if (aac_afh_get_chunk(n, c, &buf, &sz) < 0)
+                       break;
+               afhi->max_chunk_size = PARA_MAX((size_t)afhi->max_chunk_size, sz);
        }
+
+       tmp = c->masc.sbr_present_flag == 1? 2048 : 1024;
+       afhi->seconds_total = tmp * afhi->chunks_total / afhi->frequency;
+       ms2tv(1000 * tmp / afhi->frequency, &afhi->chunk_tv);
+
+       if (aac_afh_get_chunk(0, c, &buf, &sz) >= 0)
+               numbytes -= buf - map;
+       afhi->bitrate = 8 * numbytes / afhi->seconds_total / 1000;
+       _aac_afh_get_taginfo(c->mp4ff, &afhi->tags);
+       ret = 1;
+close:
+       aac_afh_close(c);
+       return ret;
 }
 
-static void aac_get_taginfo(char *buf, size_t buflen, struct afh_info *afhi)
+static uint32_t aac_afh_meta_read_cb(void *user_data, void *dest, uint32_t want)
 {
-       int i;
-       uint64_t subsize;
-       char type[5];
-
-       for (i = 0; i + 24 < buflen; i++) {
-               char *p = buf + i;
-               if (p[0] != 'm' || p[1] != 'e' || p[2] != 't' || p[3] != 'a')
-                       continue;
-               PARA_INFO_LOG("found metadata at offset %d\n", i);
-               i += 8;
-               p = buf + i;
-               i += read_atom_header(p, &subsize, type);
-               p = buf + i;
-               return read_meta(p, buflen - i, afhi);
-       }
-       PARA_INFO_LOG("no meta data\n");
+       int fd = *(int *)user_data;
+       return read(fd, dest, want);
 }
 
-static ssize_t aac_compute_chunk_table(struct afh_info *afhi,
-               char *map, size_t numbytes)
+static uint32_t aac_afh_meta_seek_cb(void *user_data, uint64_t pos)
 {
-       int ret, i;
-       size_t sum = 0;
-       off_t skip;
+       int fd = *(int *)user_data;
+       return lseek(fd, pos, SEEK_SET);
+}
 
-       ret = aac_find_stsz(map, numbytes, &skip);
-       if (ret < 0)
-               return ret;
-       afhi->chunks_total = ret;
-       PARA_DEBUG_LOG("sz table has %" PRIu32 " entries\n", afhi->chunks_total);
-       afhi->chunk_table = para_malloc((afhi->chunks_total + 1) * sizeof(size_t));
-       for (i = 1; i <= afhi->chunks_total; i++) {
-               if (skip + 4 > numbytes)
-                       break;
-               sum += read_u32_be(map + skip);
-               afhi->chunk_table[i] = sum;
-               skip += 4;
-//             if (i < 10 || i + 10 > afhi->chunks_total)
-//                     PARA_DEBUG_LOG("offset #%d: %zu\n", i, afhi->chunk_table[i]);
-       }
-       return skip;
+static uint32_t aac_afh_meta_write_cb(void *user_data, void *dest, uint32_t want)
+{
+       int fd = *(int *)user_data;
+       return write(fd, dest, want);
 }
 
-static int aac_set_chunk_tv(struct afh_info *afhi,
-               mp4AudioSpecificConfig *mp4ASC, uint32_t *seconds)
+static uint32_t aac_afh_meta_truncate_cb(void *user_data)
 {
-       float tmp = mp4ASC->sbr_present_flag == 1? 2047 : 1023;
-       struct timeval total;
-       long unsigned ms;
-
-       if (!mp4ASC->samplingFrequency)
-               return -E_MP4ASC;
-       ms = 1000.0 * afhi->chunks_total * tmp / mp4ASC->samplingFrequency;
-       ms2tv(ms, &total);
-       tv_divide(afhi->chunks_total, &total, &afhi->chunk_tv);
-       PARA_INFO_LOG("%luHz, %lus (%" PRIu32 " x %lums)\n",
-               mp4ASC->samplingFrequency, ms / 1000,
-               afhi->chunks_total, tv2ms(&afhi->chunk_tv));
-       if (ms < 1000)
-               return -E_MP4ASC;
-       *seconds = ms / 1000;
-       return 1;
+       int fd = *(int *)user_data;
+       off_t offset = lseek(fd, 0, SEEK_CUR);
+       return ftruncate(fd, offset);
 }
 
-/*
- * Init m4a file and write some tech data to given pointers.
- */
-static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
-               struct afh_info *afhi)
+static void replace_tag(mp4ff_tag_t *tag, const char *new_val, bool *found)
 {
-       int i;
-       size_t skip;
-       ssize_t ret;
-       unsigned long rate = 0, decoder_len;
-       unsigned char channels = 0;
-       mp4AudioSpecificConfig mp4ASC;
-       NeAACDecHandle handle = NULL;
-
-       ret = aac_find_esds(map, numbytes, &skip, &decoder_len);
-       if (ret < 0)
-               goto out;
-       aac_get_taginfo(map, numbytes, afhi);
-       handle = aac_open();
-       ret = -E_AAC_AFH_INIT;
-       if (NeAACDecInit(handle, (unsigned char *)map + skip, decoder_len,
-                       &rate, &channels))
-               goto out;
-       if (!channels)
-               goto out;
-       PARA_DEBUG_LOG("rate: %lu, channels: %d\n", rate, channels);
-       ret = -E_MP4ASC;
-       if (NeAACDecAudioSpecificConfig((unsigned char *)map + skip,
-                       numbytes - skip, &mp4ASC))
-               goto out;
-       if (!mp4ASC.samplingFrequency)
-               goto out;
-       ret = aac_compute_chunk_table(afhi, map, numbytes);
-       if (ret < 0)
-               goto out;
-       skip = ret;
-       ret = aac_set_chunk_tv(afhi, &mp4ASC, &afhi->seconds_total);
-       if (ret < 0)
-               goto out;
-       ret = aac_find_entry_point(map + skip, numbytes - skip, &skip);
-       if (ret < 0)
-               goto out;
-       afhi->chunk_table[0] = ret;
-       for (i = 1; i<= afhi->chunks_total; i++)
-               afhi->chunk_table[i] += ret;
-       afhi->channels = channels;
-       afhi->frequency = rate;
-       ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */
-       ret += (channels * afhi->seconds_total * 500); /* avoid rounding error */
-       afhi->bitrate = ret / (channels * afhi->seconds_total * 1000);
-       ret = 1;
-out:
-       if (handle)
-               NeAACDecClose(handle);
-       return ret;
+       free(tag->value);
+       tag->value = para_strdup(new_val);
+       *found = true;
 }
 
-static int aac_rewrite_tags(const char *map, size_t mapsize,
-               struct taginfo *tags, int fd, const char *filename)
+static void add_tag(mp4ff_metadata_t *md, const char *item, const char *value)
 {
-       MP4FileHandle h;
-       const MP4Tags *mdata;
-       int ret = write_all(fd, map, mapsize);
+       md->tags[md->count].item = para_strdup(item);
+       md->tags[md->count].value = para_strdup(value);
+       md->count++;
+}
 
+static int aac_afh_rewrite_tags(const char *map, size_t mapsize,
+               struct taginfo *tags, int fd, __a_unused const char *filename)
+{
+       int ret, i;
+       int32_t rv;
+       mp4ff_metadata_t metadata;
+       mp4ff_t *mp4ff;
+       mp4ff_callback_t cb = {
+               .read = aac_afh_meta_read_cb,
+               .seek = aac_afh_meta_seek_cb,
+               .write = aac_afh_meta_write_cb,
+               .truncate = aac_afh_meta_truncate_cb,
+               .user_data = &fd
+       };
+       bool found_artist = false, found_title = false, found_album = false,
+               found_year = false, found_comment = false;
+
+       ret = write_all(fd, map, mapsize);
        if (ret < 0)
                return ret;
        lseek(fd, 0, SEEK_SET);
-       h = MP4Modify(filename, 0);
-       if (!h) {
-               PARA_ERROR_LOG("MP4Modify() failed, fd = %d\n", fd);
-               return -E_MP4V2;
-       }
-       mdata = MP4TagsAlloc();
-       assert(mdata);
-       if (!MP4TagsFetch(mdata, h)) {
-               PARA_ERROR_LOG("MP4Tags_Fetch() failed\n");
-               ret = -E_MP4V2;
-               goto close;
-       }
 
-       if (!MP4TagsSetAlbum(mdata, tags->album)) {
-               PARA_ERROR_LOG("Could not set album\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
-       if (!MP4TagsSetArtist(mdata, tags->artist)) {
-               PARA_ERROR_LOG("Could not set album\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
-       if (!MP4TagsSetComments(mdata, tags->comment)) {
-               PARA_ERROR_LOG("Could not set comment\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
-       if (!MP4TagsSetName(mdata, tags->title)) {
-               PARA_ERROR_LOG("Could not set title\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
-       if (!MP4TagsSetReleaseDate(mdata, tags->year)) {
-               PARA_ERROR_LOG("Could not set release date\n");
-               ret = -E_MP4V2;
-               goto tags_free;
-       }
+       mp4ff = mp4ff_open_read_metaonly(&cb);
+       if (!mp4ff)
+               return -E_MP4FF_OPEN;
 
-       if (!MP4TagsStore(mdata, h)) {
-               PARA_ERROR_LOG("Could not store tags\n");
-               ret = -E_MP4V2;
-               goto tags_free;
+       ret = -E_MP4FF_META_READ;
+       rv = mp4ff_meta_get_num_items(mp4ff);
+       if (rv < 0)
+               goto close;
+       metadata.count = rv;
+       PARA_NOTICE_LOG("%d metadata item(s) found\n", rv);
+
+       metadata.tags = para_malloc((metadata.count + 5) * sizeof(mp4ff_tag_t));
+       for (i = 0; i < metadata.count; i++) {
+               mp4ff_tag_t *tag = metadata.tags + i;
+
+               ret = -E_MP4FF_META_READ;
+               if (!mp4ff_meta_get_by_index(mp4ff, i, &tag->item, &tag->value))
+                       goto free_tags;
+               PARA_INFO_LOG("found: %s: %s\n", tag->item, tag->value);
+               if (!strcmp(tag->item, "artist"))
+                       replace_tag(tag, tags->artist, &found_artist);
+               else if (!strcmp(tag->item, "title"))
+                       replace_tag(tag, tags->title, &found_title);
+               else if (!strcmp(tag->item, "album"))
+                       replace_tag(tag, tags->album, &found_album);
+               else if (!strcmp(tag->item, "date"))
+                       replace_tag(tag, tags->year, &found_year);
+               else if (!strcmp(tag->item, "comment"))
+                       replace_tag(tag, tags->comment, &found_comment);
        }
+       if (!found_artist)
+               add_tag(&metadata, "artist", tags->artist);
+       if (!found_title)
+               add_tag(&metadata, "title", tags->title);
+       if (!found_album)
+               add_tag(&metadata, "album", tags->album);
+       if (!found_year)
+               add_tag(&metadata, "date", tags->year);
+       if (!found_comment)
+               add_tag(&metadata, "comment", tags->comment);
+       ret = -E_MP4FF_META_WRITE;
+       if (!mp4ff_meta_update(&cb, &metadata))
+               goto free_tags;
        ret = 1;
-tags_free:
-       MP4TagsFree(mdata);
+free_tags:
+       for (; i > 0; i--) {
+               free(metadata.tags[i - 1].item);
+               free(metadata.tags[i - 1].value);
+       }
+       free(metadata.tags);
 close:
-       MP4Close(h, 0);
+       mp4ff_close(mp4ff);
        return ret;
 }
 
 static const char * const aac_suffixes[] = {"m4a", "mp4", NULL};
+
 /**
- * the init function of the aac audio format handler
+ * The audio format handler for the Advanced Audio Codec.
  *
- * \param afh pointer to the struct to initialize
+ * This is only compiled in if the faad library is installed.
  */
-void aac_afh_init(struct audio_format_handler *afh)
-{
-       afh->get_file_info = aac_get_file_info,
-       afh->suffixes = aac_suffixes;
-       afh->rewrite_tags = aac_rewrite_tags;
-}
+const struct audio_format_handler aac_afh = {
+       .get_file_info = aac_get_file_info,
+       .suffixes = aac_suffixes,
+       .rewrite_tags = aac_afh_rewrite_tags,
+       .open = aac_afh_open,
+       .get_chunk = aac_afh_get_chunk,
+       .close = aac_afh_close,
+};
diff --git a/aac_common.c b/aac_common.c
deleted file mode 100644 (file)
index 812c742..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-/*
- * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker,
- * Ahead Software AG
- */
-
-/** \file aac_common.c Common functions of aac_afh and aadcec. */
-
-#include "para.h"
-#include "aac.h"
-#include "error.h"
-#include "portable_io.h"
-
-/**
- * Get a new libfaad decoder handle.
- *
- * \return The handle returned by NeAACDecOpen().
- */
-NeAACDecHandle aac_open(void)
-{
-       NeAACDecHandle h = NeAACDecOpen();
-       NeAACDecConfigurationPtr c = NeAACDecGetCurrentConfiguration(h);
-
-       c->defObjectType = LC;
-       c->outputFormat = FAAD_FMT_16BIT;
-       c->downMatrix = 0;
-       NeAACDecSetConfiguration(h, c);
-       return h;
-}
-
-static unsigned long aac_read_decoder_length(char *buf, int *description_len)
-{
-       uint8_t b;
-       uint8_t numBytes = 0;
-       unsigned long length = 0;
-
-       do {
-               b = buf[numBytes];
-               numBytes++;
-               length = (length << 7) | (b & 0x7F);
-       } while
-               ((b & 0x80) && numBytes < 4);
-       *description_len = numBytes;
-       return length;
-}
-
-/**
- * search for the position and the length of the decoder configuration
- *
- * \param buf buffer to seach
- * \param buflen length of \a buf
- * \param skip Upon succesful return, this contains the offset in \a buf where
- * the decoder config starts.
- * \param decoder_length result pointer that is filled in with the length of
- * the decoder configuration on success.
- *
- * \return positive on success, negative on errors
- */
-int aac_find_esds(char *buf, size_t buflen, size_t *skip,
-               unsigned long *decoder_length)
-{
-       size_t i;
-
-       for (i = 0; i + 4 < buflen; i++) {
-               char *p = buf + i;
-               int description_len;
-
-               if (p[0] != 'e' || p[1] != 's' || p[2] != 'd' || p[3] != 's')
-                       continue;
-               i += 8;
-               p = buf + i;
-               PARA_INFO_LOG("found esds@%zu, next: %x\n", i, (unsigned)*p);
-               if (*p == 3)
-                       i += 8;
-               else
-                       i += 6;
-               p = buf + i;
-               PARA_INFO_LOG("next: %x\n", (unsigned)*p);
-               if (*p != 4)
-                       continue;
-               i += 18;
-               p = buf + i;
-               PARA_INFO_LOG("next: %x\n", (unsigned)*p);
-               if (*p != 5)
-                       continue;
-               i++;
-               p = buf + i;
-               *decoder_length = aac_read_decoder_length(p, &description_len);
-               PARA_INFO_LOG("decoder length: %lu\n", *decoder_length);
-               i += description_len;
-               *skip = i;
-               return 1;
-       }
-       return -E_ESDS;
-}
-
-/**
- * search for the first entry in the stco table
- *
- * \param buf buffer to seach
- * \param buflen length of \a buf
- * \param skip Upon succesful return, this contains the number
- * of bytes to skip from the input buffer.
- *
- * \return the position of the first entry in the table on success,
- * -E_STCO on errors.
- */
-ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip)
-{
-       ssize_t ret;
-       size_t i;
-
-       for (i = 0; i + 20 < buflen; i++) {
-               char *p = buf + i;
-
-               if (p[0] != 's' || p[1] != 't' || p[2] != 'c' || p[3] != 'o')
-                       continue;
-               PARA_INFO_LOG("found stco@%zu\n", i);
-               i += 12;
-               ret = read_u32_be(buf + i); /* first offset */
-               i += 4;
-               PARA_INFO_LOG("entry point: %zd\n", ret);
-               *skip = i;
-               return ret;
-       }
-       PARA_WARNING_LOG("stco not found, buflen: %zu\n", buflen);
-       return -E_STCO;
-}
index 5725ce043089361092ddb68e05bb3c60fcf66801..a2459d82b31991a8e9ac578e0559a398c83f4e10 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 /*
  * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker,
  * Ahead Software AG
 /** \file aacdec_filter.c paraslash's aac (m4a) decoder. */
 
 #include <regex.h>
+#include <neaacdec.h>
 
 #include "para.h"
+#include "portable_io.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "error.h"
 #include "string.h"
-#include "aac.h"
 
 /** Give up decoding after that many errors. */
 #define MAX_ERRORS 20
 /**
  * data specific to the aacdec filter
  *
- * \sa filter, filter_node
+ * \sa \ref filter, \ref filter_node.
  */
 struct private_aacdec_data {
        /** the return value of aac_open */
        NeAACDecHandle handle;
-       /** info about the currently decoded frame */
-       NeAACDecFrameInfo frame_info;
        /** whether this instance of the aac decoder is already initialized */
-       int initialized;
-       /**
-        * return value of aac_find_esds(). Used to call the right aacdec
-        * init function
-        */
-       unsigned long decoder_length;
+       bool initialized;
        /** number of times the decoder returned an error */
        unsigned error_count;
        /** number of bytes already consumed from the imput stream */
        size_t consumed_total;
-       /** return value of aac_find_entry_point */
-       size_t entry;
        /** The number of channels of the current stream. */
        unsigned int channels;
        /** Current sample rate in Hz. */
@@ -64,11 +51,18 @@ static int aacdec_execute(struct btr_node *btrn, const char *cmd, char **result)
 
 static void aacdec_open(struct filter_node *fn)
 {
+       NeAACDecConfigurationPtr c;
        struct private_aacdec_data *padd = para_calloc(sizeof(*padd));
 
+       padd->handle = NeAACDecOpen();
+       c = NeAACDecGetCurrentConfiguration(padd->handle);
+       c->defObjectType = LC;
+       c->outputFormat = FAAD_FMT_16BIT;
+       c->downMatrix = 0;
+       NeAACDecSetConfiguration(padd->handle, c);
+
        fn->private_data = padd;
        fn->min_iqs = 2048;
-       padd->handle = aac_open();
 }
 
 static void aacdec_close(struct filter_node *fn)
@@ -86,9 +80,9 @@ static int aacdec_post_select(__a_unused struct sched *s, void *context)
        struct btr_node *btrn = fn->btrn;
        struct private_aacdec_data *padd = fn->private_data;
        int i, ret;
-       char *p, *inbuf, *outbuffer;
-       char *btr_buf;
-       size_t len, skip, consumed, loaded;
+       char *inbuf, *outbuf, *btrbuf;
+       size_t len, consumed, loaded = 0;
+       NeAACDecFrameInfo frame_info;
 
 next_buffer:
        ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
@@ -103,121 +97,68 @@ next_buffer:
        if (!padd->initialized) {
                unsigned long rate = 0;
                unsigned char channels = 0;
-               ret = aac_find_esds(inbuf, len, &skip, &padd->decoder_length);
+               ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf,
+                       len, &rate, &channels);
+               PARA_INFO_LOG("decoder init: %d\n", ret);
                if (ret < 0) {
-                       PARA_INFO_LOG("%s\n", para_strerror(-ret));
-                       ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf,
-                               len, &rate, &channels);
-                       PARA_INFO_LOG("decoder init: %d\n", ret);
-                       if (ret < 0) {
-                               ret = -E_AACDEC_INIT;
-                               goto out;
-                       }
-                       consumed = ret;
-               } else {
-                       PARA_INFO_LOG("decoder len: %lu\n",
-                               padd->decoder_length);
-                       consumed += skip;
-                       p = inbuf + consumed;
                        ret = -E_AACDEC_INIT;
-                       if (NeAACDecInit2(padd->handle, (unsigned char *)p,
-                                       padd->decoder_length, &rate,
-                                       &channels) != 0)
-                               goto out;
+                       goto err;
                }
+               consumed = ret;
                padd->sample_rate = rate;
                padd->channels = channels;
                PARA_INFO_LOG("rate: %u, channels: %u\n",
                        padd->sample_rate, padd->channels);
-               padd->initialized = 1;
+               padd->initialized = true;
        }
-       if (padd->decoder_length > 0) {
-               consumed = 0;
-               if (!padd->entry) {
-                       ret = aac_find_entry_point(inbuf + consumed,
-                               len - consumed, &skip);
-                       if (ret < 0) {
-                               ret = len;
-                               goto out;
-                       }
-                       consumed += skip;
-                       padd->entry = ret;
-                       PARA_INFO_LOG("entry: %zu\n", padd->entry);
-               }
-               ret = len;
-               if (padd->consumed_total + len < padd->entry)
-                       goto out;
-               if (padd->consumed_total < padd->entry)
-                       consumed = padd->entry - padd->consumed_total;
-       }
-       for (; consumed < len; consumed++)
-               if ((inbuf[consumed] & 0xfe) == 0x20)
-                       break;
        if (consumed >= len)
                goto success;
-       p = inbuf + consumed;
        //PARA_CRIT_LOG("consumed: %zu (%zu + %zu), have: %zu\n", padd->consumed_total + consumed,
        //      padd->consumed_total, consumed, len - consumed);
-       outbuffer = NeAACDecDecode(padd->handle, &padd->frame_info,
-               (unsigned char *)p, len - consumed);
-       if (padd->frame_info.error) {
-               int err = padd->frame_info.error;
+       outbuf = NeAACDecDecode(padd->handle, &frame_info,
+               (unsigned char *)inbuf + consumed, len - consumed);
+       if (frame_info.error) {
+               int err = frame_info.error;
                ret = -E_AAC_DECODE;
                if (padd->error_count++ > MAX_ERRORS)
                        goto err;
-               /* Suppress non-fatal bitstream error message at BOF/EOF */
-               if (len < fn->min_iqs || padd->consumed_total == 0) {
-                       consumed = len;
-                       goto success;
-               }
-               PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage(err));
-               PARA_ERROR_LOG("consumed: %zu + %zu + %lu\n",
+               PARA_NOTICE_LOG("error #%u: (%s)\n", padd->error_count,
+                       NeAACDecGetErrorMessage(err));
+               PARA_NOTICE_LOG("consumed (total, buffer, frame): "
+                       "%zu, %zu, %lu\n",
                        padd->consumed_total, consumed,
-                       padd->frame_info.bytesconsumed);
-               if (consumed < len)
-                       consumed++; /* catch 21 */
+                       frame_info.bytesconsumed);
+               consumed++; /* just eat one byte and hope for the best */
                goto success;
        }
        padd->error_count = 0;
-       //PARA_CRIT_LOG("decoder ate %lu\n", padd->frame_info.bytesconsumed);
-       consumed += padd->frame_info.bytesconsumed;
-       ret = consumed;
-       if (!padd->frame_info.samples)
-               goto out;
-       btr_buf = para_malloc(2 * padd->frame_info.samples);
-       loaded = 0;
-       for (i = 0; i < padd->frame_info.samples; i++) {
-               short sh = ((short *)outbuffer)[i];
-               write_int16_host_endian(btr_buf + loaded, sh);
+       //PARA_CRIT_LOG("decoder ate %lu\n", frame_info.bytesconsumed);
+       consumed += frame_info.bytesconsumed;
+       if (!frame_info.samples)
+               goto success;
+       btrbuf = para_malloc(2 * frame_info.samples);
+       for (i = 0; i < frame_info.samples; i++) {
+               short sh = ((short *)outbuf)[i];
+               write_int16_host_endian(btrbuf + loaded, sh);
                loaded += 2;
        }
-       btr_add_output(btr_buf, loaded, btrn);
+       btr_add_output(btrbuf, loaded, btrn);
 success:
-       ret = consumed;
-out:
-       if (ret >= 0) {
-               padd->consumed_total += ret;
-               btr_consume(btrn, ret);
+       btr_consume(btrn, consumed);
+       padd->consumed_total += consumed;
+       if (loaded == 0)
                goto next_buffer;
-       }
+       return 1;
 err:
        assert(ret < 0);
        btr_remove_node(&fn->btrn);
        return ret;
 }
 
-/**
- * The init function of the aacdec filter.
- *
- * \param f Pointer to the filter struct to initialize.
- *
- * \sa filter::init
- */
-void aacdec_filter_init(struct filter *f)
-{
-       f->open = aacdec_open;
-       f->close = aacdec_close;
-       f->pre_select = generic_filter_pre_select;
-       f->post_select = aacdec_post_select;
-       f->execute = aacdec_execute;
-}
+const struct filter lsg_filter_cmd_com_aacdec_user_data = {
+       .open = aacdec_open,
+       .close = aacdec_close,
+       .pre_select = generic_filter_pre_select,
+       .post_select = aacdec_post_select,
+       .execute = aacdec_execute
+};
diff --git a/acl.c b/acl.c
index 560ff9999d191f418c35695f4a6629b45436f49f..2c90052658eda947ea586a55f292dc85f24e68f7 100644 (file)
--- a/acl.c
+++ b/acl.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file acl.c Access control lists for paraslash senders. */
 
@@ -35,10 +31,12 @@ struct access_info {
 /**
  * Return true if addr_1 matches addr_2 in the first `netmask' bits.
  */
-static int v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask)
+static bool v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask)
 {
        uint32_t mask = ~0U;
 
+       if (netmask == 0) /* avoid 32-bit shift, which is undefined in C. */
+               return true;
        if (netmask < 32)
                mask <<= (32 - netmask);
        return (htonl(addr_1) & mask) == (htonl(addr_2) & mask);
@@ -81,7 +79,7 @@ no_match:
  * \param addr The address to add.
  * \param netmask The netmask to use for this entry.
  */
-static void acl_add_entry(struct list_head *acl, char *addr, int netmask)
+void acl_add_entry(struct list_head *acl, char *addr, int netmask)
 {
        struct access_info *ai = para_malloc(sizeof(struct access_info));
 
@@ -103,14 +101,18 @@ static void acl_del_entry(struct list_head *acl, char *addr, unsigned netmask)
        struct access_info *ai, *tmp;
        struct in_addr to_delete;
 
+       PARA_NOTICE_LOG("removing entries matching %s/%u\n", addr, netmask);
        inet_pton(AF_INET, addr, &to_delete);
 
        list_for_each_entry_safe(ai, tmp, acl, node) {
-
                if (v4_addr_match(to_delete.s_addr, ai->addr.s_addr,
                                        PARA_MIN(netmask, ai->netmask))) {
-                       PARA_NOTICE_LOG("removing %s/%u from access list\n",
-                                       addr, ai->netmask);
+                       char dst[INET_ADDRSTRLEN + 1];
+                       const char *p = inet_ntop(AF_INET, &ai->addr.s_addr,
+                               dst, sizeof(dst));
+                       if (p)
+                               PARA_INFO_LOG("removing %s/%u\n", p,
+                                       ai->netmask);
                        list_del(&ai->node);
                        free(ai);
                }
@@ -139,27 +141,6 @@ char *acl_get_contents(struct list_head *acl)
        return ret;
 }
 
-/**
- * Initialize an access control list.
- *
- * \param acl The list to initialize.
- * \param acl_info An array of strings of the form ip/netmask.
- * \param num The number of strings in \a acl_info.
- */
-void acl_init(struct list_head *acl, char * const *acl_info, int num)
-{
-       char    addr[16];
-       int     mask, i;
-
-       INIT_LIST_HEAD(acl);
-       for (i = 0; i < num; i++)
-               if (parse_cidr(acl_info[i], addr, sizeof(addr), &mask) == NULL)
-                       PARA_CRIT_LOG("ACL syntax error: %s, ignoring\n",
-                                     acl_info[i]);
-               else
-                       acl_add_entry(acl, addr, mask);
-}
-
 /**
  * Check whether the peer name of a given fd is allowed by an acl.
  *
diff --git a/acl.h b/acl.h
index 5bfa39f23b2c087456471e06ea3d55b816fffbaf..02002e57a7d2140c633f1b70965fcf800e2fe660 100644 (file)
--- a/acl.h
+++ b/acl.h
@@ -1,12 +1,8 @@
-/*
- * Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file acl.h Exported functions of acl.c. */
 
-void acl_init(struct list_head *acl, char * const *acl_info, int num);
+void acl_add_entry(struct list_head *acl, char *addr, int netmask);
 char *acl_get_contents(struct list_head *acl);
 int acl_check_access(int fd, struct list_head *acl, int default_deny);
 void acl_allow(char *addr, int mask, struct list_head *acl, int default_deny);
diff --git a/afh.c b/afh.c
index 36c432e54546d7f6079f9eb0ea4b35b675ab806b..c896a7d1eff4a843e2e61a4ec8b6dac4982ed197 100644 (file)
--- a/afh.c
+++ b/afh.c
@@ -1,26 +1,28 @@
-/*
- * Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afh.c Paraslash's standalone audio format handler tool. */
 
 #include <regex.h>
+#include <lopsub.h>
 
+#include "afh.lsg.h"
 #include "para.h"
 #include "string.h"
-#include "afh.cmdline.h"
 #include "fd.h"
 #include "afh.h"
 #include "error.h"
 #include "version.h"
-#include "ggo.h"
 
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
-static struct afh_args_info conf;
+static struct lls_parse_result *lpr;
+
+#define CMD_PTR (lls_cmd(0, afh_suite))
+#define OPT_RESULT(_name) (lls_opt_result(LSG_AFH_PARA_AFH_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
 
 static int loglevel;
 INIT_STDERR_LOGGING(loglevel)
@@ -37,34 +39,38 @@ static int rewrite_tags(const char *name, int input_fd, void *map,
        struct taginfo *tags = &afhi->tags;
        bool modified = false;
        char *tmp_name;
+       const char *arg;
        int output_fd = -1, ret;
        struct stat sb;
 
-       if (tag_needs_update(conf.year_given, tags->year, conf.year_arg)) {
+       arg = OPT_STRING_VAL(YEAR);
+       if (tag_needs_update(OPT_GIVEN(YEAR), tags->year, arg)) {
                free(tags->year);
-               tags->year = para_strdup(conf.year_arg);
+               tags->year = para_strdup(arg);
                modified = true;
        }
-       if (tag_needs_update(conf.title_given, tags->title, conf.title_arg)) {
+       arg = OPT_STRING_VAL(TITLE);
+       if (tag_needs_update(OPT_GIVEN(TITLE), tags->title, arg)) {
                free(tags->title);
-               tags->title = para_strdup(conf.title_arg);
+               tags->title = para_strdup(arg);
                modified = true;
        }
-       if (tag_needs_update(conf.artist_given, tags->artist,
-                       conf.artist_arg)) {
+       arg = OPT_STRING_VAL(ARTIST);
+       if (tag_needs_update(OPT_GIVEN(ARTIST), tags->artist, arg)) {
                free(tags->artist);
-               tags->artist = para_strdup(conf.artist_arg);
+               tags->artist = para_strdup(arg);
                modified = true;
        }
-       if (tag_needs_update(conf.album_given, tags->album, conf.album_arg)) {
+       arg = OPT_STRING_VAL(ALBUM);
+       if (tag_needs_update(OPT_GIVEN(ALBUM), tags->album, arg)) {
                free(tags->album);
-               tags->album = para_strdup(conf.album_arg);
+               tags->album = para_strdup(arg);
                modified = true;
        }
-       if (tag_needs_update(conf.comment_given, tags->comment,
-                       conf.comment_arg)) {
+       arg = OPT_STRING_VAL(COMMENT);
+       if (tag_needs_update(OPT_GIVEN(COMMENT), tags->comment, arg)) {
                free(tags->comment);
-               tags->comment = para_strdup(conf.comment_arg);
+               tags->comment = para_strdup(arg);
                modified = true;
        }
        if (!modified) {
@@ -99,7 +105,7 @@ static int rewrite_tags(const char *name, int input_fd, void *map,
                tmp_name);
        if (ret < 0)
                goto out;
-       if (conf.backup_given) {
+       if (OPT_GIVEN(BACKUP)) {
                char *backup_name = make_message("%s~", name);
                ret = xrename(name, backup_name);
                free(backup_name);
@@ -107,6 +113,24 @@ static int rewrite_tags(const char *name, int input_fd, void *map,
                        goto out;
        }
        ret = xrename(tmp_name, name);
+       if (ret < 0)
+               goto out;
+       if (OPT_GIVEN(PRESERVE)) {
+               struct timespec times[2]; /* [0]: atime, [1]: mtime */
+               times[0].tv_nsec = UTIME_OMIT;
+               times[1] = sb.st_mtim;
+               /*
+                * We might well have written a file of identical size. If we
+                * keep the mtime as well, we might fool backup applications
+                * like rsync which skip files whose size and mtime haven't
+                * changed. So we change the mtime slightly.
+                */
+               times[1].tv_sec++;
+               if (futimens(output_fd, times) < 0) {
+                       ret = -ERRNO_TO_PARA_ERROR(errno);
+                       goto out;
+               }
+       }
 out:
        if (ret < 0 && output_fd >= 0)
                unlink(tmp_name); /* ignore errors */
@@ -125,39 +149,53 @@ static void print_info(int audio_format_num, struct afh_info *afhi)
        free(msg);
 }
 
-static void print_chunk_table(struct afh_info *afhi)
+static void print_chunk_table(struct afh_info *afhi, int audio_format_id,
+               const void *map, size_t mapsize)
 {
-       int i;
+       int i, ret;
+       void *ctx = NULL;
 
-       if (conf.parser_friendly_given) {
-               printf("chunk_table: ");
-               for (i = 0; i <= afhi->chunks_total; i++)
-                       printf("%u ", afhi->chunk_table[i]);
-               printf("\n");
-               return;
-       }
-       for (i = 1; i <= afhi->chunks_total; i++) {
+       for (i = 0; i < afhi->chunks_total; i++) {
                struct timeval tv;
                long unsigned from, to;
-               tv_scale(i - 1, &afhi->chunk_tv, &tv);
-               from = tv2ms(&tv);
+               const char *buf;
+               size_t len;
                tv_scale(i, &afhi->chunk_tv, &tv);
+               from = tv2ms(&tv);
+               tv_scale(i + 1, &afhi->chunk_tv, &tv);
                to = tv2ms(&tv);
-               printf("%d [%lu.%03lu - %lu.%03lu] %u - %u (%u)\n", i - 1,
-                       from / 1000, from % 1000, to / 1000, to % 1000,
-                       afhi->chunk_table[i - 1], afhi->chunk_table[i],
-                       afhi->chunk_table[i] - afhi->chunk_table[i - 1]);
+               ret = afh_get_chunk(i, afhi, audio_format_id, map, mapsize,
+                       &buf, &len, &ctx);
+               if (ret < 0) {
+                       PARA_ERROR_LOG("fatal: chunk %d: %s\n", i,
+                               para_strerror(-ret));
+                       return;
+               }
+               if (!OPT_GIVEN(PARSER_FRIENDLY))
+                       printf("%d [%lu.%03lu - %lu.%03lu] ", i, from / 1000,
+                               from % 1000, to / 1000, to % 1000);
+               printf("%td - %td", buf - (const char *)map,
+                       buf + len - (const char *)map);
+               if (!OPT_GIVEN(PARSER_FRIENDLY))
+                       printf(" (%zu)", len);
+               printf("\n");
        }
+       afh_close(ctx, audio_format_id);
 }
 
-__noreturn static void print_help_and_die(void)
+static void handle_help_flags(void)
 {
-       struct ggo_help h = DEFINE_GGO_HELP(afh);
-       int d = conf.detailed_help_given;
-       unsigned flags = d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS;
+       char *help;
 
-       ggo_print_help(&h, flags);
-       printf("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
+       if (OPT_GIVEN(DETAILED_HELP))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP) || lls_num_inputs(lpr) == 0)
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s", help);
+       free(help);
+       printf("Supported audio formats\n  %s\n", AUDIO_FORMAT_HANDLERS);
        exit(EXIT_SUCCESS);
 }
 
@@ -175,34 +213,36 @@ int main(int argc, char **argv)
        void *audio_file_data;
        size_t audio_file_size;
        struct afh_info afhi;
+       char *errctx;
 
-       afh_cmdline_parser(argc, argv, &conf);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       version_handle_flag("afh", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given || conf.inputs_num == 0)
-               print_help_and_die();
-       afh_init();
-       for (i = 0; i < conf.inputs_num; i++) {
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       version_handle_flag("afh", OPT_GIVEN(VERSION));
+       handle_help_flags();
+       for (i = 0; i < lls_num_inputs(lpr); i++) {
                int ret2;
-               ret = mmap_full_file(conf.inputs[i], O_RDONLY, &audio_file_data,
+               const char *path = lls_input(i, lpr);
+               ret = mmap_full_file(path, O_RDONLY, &audio_file_data,
                        &audio_file_size, &fd);
                if (ret < 0) {
-                       PARA_ERROR_LOG("failed to mmap \"%s\"\n", conf.inputs[i]);
+                       PARA_ERROR_LOG("failed to mmap \"%s\"\n", path);
                        goto out;
                }
-               ret = compute_afhi(conf.inputs[i], audio_file_data, audio_file_size,
+               ret = compute_afhi(path, audio_file_data, audio_file_size,
                        fd, &afhi);
                if (ret >= 0) {
                        audio_format_num = ret;
-                       if (conf.modify_given) {
-                               ret = rewrite_tags(conf.inputs[i], fd, audio_file_data,
+                       if (OPT_GIVEN(MODIFY)) {
+                               ret = rewrite_tags(path, fd, audio_file_data,
                                        audio_file_size, audio_format_num, &afhi);
                        } else {
-                               printf("File %d: %s\n", i + 1, conf.inputs[i]);
+                               printf("File %d: %s\n", i + 1, path);
                                print_info(audio_format_num, &afhi);
-                               if (conf.chunk_table_given)
-                                       print_chunk_table(&afhi);
-                               printf("\n");
+                               if (OPT_GIVEN(CHUNK_TABLE))
+                                       print_chunk_table(&afhi, audio_format_num,
+                                               audio_file_data, audio_file_size);
                        }
                        clear_afhi(&afhi);
                }
@@ -214,7 +254,10 @@ int main(int argc, char **argv)
                        break;
        }
 out:
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", errctx);
        if (ret < 0)
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       lls_free_parse_result(lpr, CMD_PTR);
        return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
 }
diff --git a/afh.h b/afh.h
index a6f9c50073bd39563d05a76f38c5377b10451b16..b3295f6e2fda111bd30ec5d20cd2431bbea325a6 100644 (file)
--- a/afh.h
+++ b/afh.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afh.h Structures for audio format handling (para_server). */
 
@@ -40,6 +36,8 @@ struct afh_info {
         * the current audio file.
         */
        uint32_t *chunk_table;
+       /** Size of the largest chunk, introduced in v0.6.0. */
+       uint32_t max_chunk_size;
        /** Period of time between sending data chunks. */
        struct timeval chunk_tv;
        /**
@@ -60,11 +58,12 @@ struct afh_info {
 
 /** Data about the current audio file, passed from afs to server. */
 struct audio_file_data {
-       /** The open file descriptor to the current audio file. */
-       int fd;
        /** Vss needs this for streaming. */
        struct afh_info afhi;
-       /** Size of the largest chunk. */
+       /**
+        * Size of the largest chunk. Superseded by afhi->max_chunk_size. May
+        * be removed after v0.6.1.
+        */
        uint32_t max_chunk_size;
        /** Needed to get the audio file header. */
        uint8_t audio_format_id;
@@ -79,14 +78,6 @@ struct audio_file_data {
  * in the other part of this struct.
  */
 struct audio_format_handler {
-       /** Name of the audio format. */
-       const char *name;
-       /**
-        * Pointer to the audio format handler's init function.
-        *
-        * Must initialize all function pointers and is assumed to succeed.
-        */
-       void (*init)(struct audio_format_handler*);
        /** Typical file endings for files that can be handled by this afh. */
        const char * const *suffixes;
        /**
@@ -98,12 +89,32 @@ struct audio_format_handler {
         * success, the function must return a positive value and fill in the
         * given struct afh_info.
         *
-        * \sa struct afh_info
+        * \sa struct \ref afh_info.
         */
        int (*get_file_info)(char *map, size_t numbytes, int fd,
-               struct afh_info *afi);
+               struct afh_info *afhi);
        /** Optional, used for header-rewriting. See \ref afh_get_header(). */
        void (*get_header)(void *map, size_t mapsize, char **buf, size_t *len);
+       /**
+        * An audio format handler may signify support for dynamic chunks by
+        * defining ->get_chunk below. In this case the vss calls ->open() at
+        * BOS, ->get_chunk() for each chunk while streaming, and ->close() at
+        * EOS. The chunk table is not accessed at all.
+        *
+        * The function may return its (opaque) context through the last
+        * argument. The returned pointer is passed to subsequent calls to
+        * ->get_chunk() and ->close().
+        */
+       int (*open)(const void *map, size_t mapsize, void **afh_context);
+       /**
+        * Return a reference to one chunk. The returned pointer points to a
+        * portion of the memory mapped audio file. The caller must not call
+        * free() on it.
+        */
+       int (*get_chunk)(long unsigned chunk_num, void *afh_context,
+               const char **buf, size_t *len);
+       /** Deallocate the resources occupied by ->open(). */
+       void (*close)(void *afh_context);
        /**
         * Write audio file with altered tags, optional.
         *
@@ -114,15 +125,16 @@ struct audio_format_handler {
                int output_fd, const char *filename);
 };
 
-void afh_init(void);
 int guess_audio_format(const char *name);
 int compute_afhi(const char *path, char *data, size_t size,
        int fd, struct afh_info *afhi);
 const char *audio_format_name(int);
-void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
-               void *map, const char **buf, size_t *len);
+__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
+               uint8_t audio_format_id, const void *map, size_t mapsize,
+               const char **buf, size_t *len, void **afh_context);
+void afh_close(void *afh_context, uint8_t audio_format_id);
 int32_t afh_get_start_chunk(int32_t approx_chunk_num,
-               const struct afh_info *afhi);
+               const struct afh_info *afhi, uint8_t audio_format_id);
 void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
                void *map, size_t mapsize, char **buf, size_t *len);
 void afh_free_header(char *header_buf, uint8_t audio_format_id);
@@ -130,3 +142,5 @@ void clear_afhi(struct afh_info *afhi);
 unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **result);
 int afh_rewrite_tags(int audio_format_id, void *map, size_t mapsize,
                struct taginfo *tags, int output_fd, const char *filename);
+void set_max_chunk_size(struct afh_info *afhi);
+bool afh_supports_dynamic_chunks(int audio_format_id);
index dfbf75132f4e4372eb0b4c95bff8ef25793dc545..a267f58b106b9f0df09d3a8dad99368c3c519401 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afh_common.c Common audio format handler functions. */
 
 #include "string.h"
 #include "afh.h"
 
-typedef void afh_init_func(struct audio_format_handler *);
-/* It does not hurt to declare init functions which are not available. */
-extern afh_init_func mp3_init, ogg_init, aac_afh_init, wma_afh_init,
-       spx_afh_init, flac_afh_init, opus_afh_init;
-
 /** The list of all status items */
-const char *status_item_list[] = {STATUS_ITEM_ARRAY};
+const char *status_item_list[] = {STATUS_ITEMS};
 
 /**
- * The list of supported audio formats.
+ * For each audio file the number of its audio format is stored in the
+ * database. Therefore this list, in particular its order, is part of the ABI.
+ * So it's only OK to append new audio formats. All audio formats are listed
+ * here, regardless of whether the audio format handler is compiled in.
+ */
+#define ALL_AUDIO_FORMATS \
+       AUDIO_FORMAT(mp3) \
+       AUDIO_FORMAT(ogg) \
+       AUDIO_FORMAT(aac) \
+       AUDIO_FORMAT(wma) \
+       AUDIO_FORMAT(spx) \
+       AUDIO_FORMAT(flac) \
+       AUDIO_FORMAT(opus) \
+
+/** \cond audio_format_handler */
+#define AUDIO_FORMAT(_fmt) #_fmt,
+static const char * const audio_format_names[] = {ALL_AUDIO_FORMATS};
+#undef AUDIO_FORMAT
+/* Weak declarations must be public. */
+#define AUDIO_FORMAT(_fmt) \
+       struct audio_format_handler _fmt ## _afh __attribute__ ((weak)) \
+       = {.get_file_info = NULL};
+ALL_AUDIO_FORMATS
+#undef AUDIO_FORMAT
+#define AUDIO_FORMAT(_fmt) & _fmt ## _afh,
+static struct audio_format_handler *afl[] = {ALL_AUDIO_FORMATS};
+#undef AUDIO_FORMAT
+#define NUM_AUDIO_FORMATS (ARRAY_SIZE(afl))
+/** \endcond audio_format_handler */
+
+/**
+ * Get the name of the given audio format.
  *
- * We always define the full array of audio formats even if some audio formats
- * were not compiled in. This is because for each audio file the number of its
- * audio format is stored in the database. We don't want that numbers to become
- * stale just because the user installed a new version of paraslash that
- * supports a different set of audio formats.
+ * \param i The audio format number.
  *
- * It can still be easily detected whether an audio format is compiled in by
- * checking if the init function pointer is not \p NULL.
+ * \return This returns a pointer to statically allocated memory so it
+ * must not be freed by the caller.
  */
-static struct audio_format_handler afl[] = {
-       {
-               .name = "mp3",
-               .init = mp3_init,
-       },
-       {
-               .name = "ogg",
-#if defined(HAVE_OGG) && defined(HAVE_VORBIS)
-               .init = ogg_init,
-#endif
-       },
-       {
-               .name = "aac",
-#if defined(HAVE_MP4V2)
-               .init = aac_afh_init,
-#endif
-       },
-       {
-               .name = "wma",
-               .init = wma_afh_init,
-       },
-       {
-               .name = "spx",
-#if defined(HAVE_OGG) && defined(HAVE_SPEEX)
-               .init = spx_afh_init,
-#endif
-       },
-       {
-               .name = "flac",
-#if defined(HAVE_OGG) && defined(HAVE_FLAC)
-               .init = flac_afh_init,
-#endif
-       },
-       {
-               .name = "opus",
-#if defined(HAVE_OGG) && defined(HAVE_OPUS)
-               .init = opus_afh_init,
-#endif
-       },
-       {
-               .name = NULL,
-       }
-};
+const char *audio_format_name(int i)
+{
+       if (i < 0 || i >= NUM_AUDIO_FORMATS)
+               return "???";
+       return audio_format_names[i];
+}
 
 static inline int next_audio_format(int format)
 {
        for (;;) {
-               if (!afl[format].name)
-                       return format;
                format++;
-               if (afl[format].init)
+               if (format >= NUM_AUDIO_FORMATS)
+                       return format;
+               if (afl[format]->get_file_info)
                        return format;
        }
-
 }
 
 /** Iterate over each supported audio format. */
-#define FOR_EACH_AUDIO_FORMAT(i) for (i = 0; afl[i].name; i = next_audio_format(i))
+#define FOR_EACH_AUDIO_FORMAT(i) \
+       for (i = 0; i < NUM_AUDIO_FORMATS; i = next_audio_format(i))
 
 /**
- * Call the init function of each supported audio format handler.
+ * Tell whether an audio format handler provides chunk tables.
+ *
+ * Each audio format handler either provides a chunk table or supports dynamic
+ * chunks.
+ *
+ * \param audio_format_id Offset in the afl array.
+ *
+ * \return True if dynamic chunks are supported, false if the audio format
+ * handler provides chunk tables.
  */
-void afh_init(void)
+bool afh_supports_dynamic_chunks(int audio_format_id)
 {
-       int i;
-
-       PARA_NOTICE_LOG("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
-       FOR_EACH_AUDIO_FORMAT(i) {
-               PARA_INFO_LOG("initializing %s handler\n",
-                       audio_format_name(i));
-               afl[i].init(&afl[i]);
-       }
+       return afl[audio_format_id]->get_chunk;
 }
 
 /**
@@ -123,8 +105,8 @@ int guess_audio_format(const char *name)
        int i,j, len = strlen(name);
 
        FOR_EACH_AUDIO_FORMAT(i) {
-               for (j = 0; afl[i].suffixes[j]; j++) {
-                       const char *p = afl[i].suffixes[j];
+               for (j = 0; afl[i]->suffixes[j]; j++) {
+                       const char *p = afl[i]->suffixes[j];
                        int plen = strlen(p);
                        if (len < plen + 1)
                                continue;
@@ -139,21 +121,6 @@ int guess_audio_format(const char *name)
        return -E_AUDIO_FORMAT;
 }
 
-/**
- * Get the name of the given audio format.
- *
- * \param i The audio format number.
- *
- * \return This returns a pointer to statically allocated memory so it
- * must not be freed by the caller.
- */
-const char *audio_format_name(int i)
-{
-       if (i < 0 || i >= ARRAY_SIZE(afl) - 1)
-               return "???";
-       return afl[i].name;
-}
-
 static int get_file_info(int format, const char *path, char *data,
                size_t size, int fd, struct afh_info *afhi)
 {
@@ -161,7 +128,7 @@ static int get_file_info(int format, const char *path, char *data,
        const char *fmt = audio_format_name(format);
 
        memset(afhi, 0, sizeof(*afhi));
-       ret = afl[format].get_file_info(data, size, fd, afhi);
+       ret = afl[format]->get_file_info(data, size, fd, afhi);
        if (ret < 0) {
                PARA_WARNING_LOG("%s: %s format not detected: %s\n",
                        path, fmt, para_strerror(-ret));
@@ -261,21 +228,73 @@ static inline size_t get_chunk_len(long unsigned chunk_num,
 /**
  * Get one chunk of audio data.
  *
+ * This implicitly calls the ->open method of the audio format handler at the
+ * first call.
+ *
  * \param chunk_num The number of the chunk to get.
  * \param afhi Describes the audio file.
+ * \param audio_format_id Determines the afh.
  * \param map The memory mapped audio file.
+ * \param mapsize Passed to the afh's ->open() method.
  * \param buf Result pointer.
  * \param len The length of the chunk in bytes.
+ * \param afh_context Value/result, determines whether ->open() is called.
  *
  * Upon return, \a buf will point so memory inside \a map. The returned buffer
  * must therefore not be freed by the caller.
+ *
+ * \return Standard.
  */
-void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
-               void *map, const char **buf, size_t *len)
+__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
+               uint8_t audio_format_id, const void *map, size_t mapsize,
+               const char **buf, size_t *len, void **afh_context)
 {
-       size_t pos = afhi->chunk_table[chunk_num];
-       *buf = map + pos;
-       *len = get_chunk_len(chunk_num, afhi);
+       struct audio_format_handler *afh = afl[audio_format_id];
+
+       if (afh_supports_dynamic_chunks(audio_format_id)) {
+               int ret;
+
+               if (!*afh_context) {
+                       ret = afh->open(map, mapsize, afh_context);
+                       if (ret < 0)
+                               return ret;
+               }
+               ret = afh->get_chunk(chunk_num, *afh_context,
+                       buf, len);
+               if (ret < 0) {
+                       afh->close(*afh_context);
+                       *afh_context = NULL;
+               }
+               return ret;
+       } else {
+               size_t pos = afhi->chunk_table[chunk_num];
+               *buf = map + pos;
+               *len = get_chunk_len(chunk_num, afhi);
+               return 0;
+       }
+}
+
+/**
+ * Deallocate resources allocated due to dynamic chunk handling.
+ *
+ * This function should be called if afh_get_chunk() was called at least once.
+ * It is OK to call it even for audio formats which do not support dynamic
+ * chunks, in which case the function does nothing.
+ *
+ * \param afh_context As returned from the ->open method of the afh.
+ * \param audio_format_id Determines the afh.
+ */
+void afh_close(void *afh_context, uint8_t audio_format_id)
+{
+       struct audio_format_handler *afh = afl[audio_format_id];
+
+       if (!afh_supports_dynamic_chunks(audio_format_id))
+               return;
+       if (!afh->close)
+               return;
+       if (!afh_context)
+               return;
+       afh->close(afh_context);
 }
 
 /**
@@ -283,16 +302,22 @@ void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
  *
  * \param approx_chunk_num Upper bound for the chunk number to return.
  * \param afhi Needed for the chunk table.
+ * \param audio_format_id Determines the afh.
  *
- * \return The first non-empty chunk <= \a approx_chunk_num.
+ * \return For audio format handlers which support dynamic chunks, the function
+ * returns the given chunk number. Otherwise it returns the first non-empty
+ * chunk <= \a approx_chunk_num.
  *
  * \sa \ref afh_get_chunk().
  */
 int32_t afh_get_start_chunk(int32_t approx_chunk_num,
-               const struct afh_info *afhi)
+               const struct afh_info *afhi, uint8_t audio_format_id)
 {
        int32_t k;
 
+       if (afh_supports_dynamic_chunks(audio_format_id))
+               return approx_chunk_num;
+
        for (k = PARA_MAX(0, approx_chunk_num); k >= 0; k--)
                if (get_chunk_len(k, afhi) > 0)
                        return k;
@@ -324,7 +349,7 @@ int32_t afh_get_start_chunk(int32_t approx_chunk_num,
 void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
                void *map, size_t mapsize, char **buf, size_t *len)
 {
-       struct audio_format_handler *afh = afl + audio_format_id;
+       struct audio_format_handler *afh = afl[audio_format_id];
 
        if (!map || !afhi || !afhi->header_len) {
                *buf = NULL;
@@ -347,7 +372,7 @@ void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
  */
 void afh_free_header(char *header_buf, uint8_t audio_format_id)
 {
-       struct audio_format_handler *afh = afl + audio_format_id;
+       struct audio_format_handler *afh = afl[audio_format_id];
 
        if (afh->get_header)
                free(header_buf);
@@ -374,29 +399,62 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re
                "%s: %" PRIu32 "\n" /* seconds total */
                "%s: %lu: %lu\n" /* chunk time */
                "%s: %" PRIu32 "\n" /* num chunks */
+               "%s: %" PRIu32 "\n" /* max chunk size */
                "%s: %s\n" /* techinfo */
                "%s: %s\n" /* artist */
                "%s: %s\n" /* title */
                "%s: %s\n" /* year */
                "%s: %s\n" /* album */
                "%s: %s\n", /* comment */
-               status_item_list[SI_BITRATE], afhi->bitrate,
-               status_item_list[SI_FORMAT], audio_format_name(audio_format_num),
-               status_item_list[SI_FREQUENCY], afhi->frequency,
-               status_item_list[SI_CHANNELS], afhi->channels,
-               status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total,
-               status_item_list[SI_CHUNK_TIME], (long unsigned)afhi->chunk_tv.tv_sec,
+               status_item_list[SI_bitrate], afhi->bitrate,
+               status_item_list[SI_format], audio_format_name(audio_format_num),
+               status_item_list[SI_frequency], afhi->frequency,
+               status_item_list[SI_channels], afhi->channels,
+               status_item_list[SI_seconds_total], afhi->seconds_total,
+               status_item_list[SI_chunk_time], (long unsigned)afhi->chunk_tv.tv_sec,
                        (long unsigned)afhi->chunk_tv.tv_usec,
-               status_item_list[SI_NUM_CHUNKS], afhi->chunks_total,
-               status_item_list[SI_TECHINFO], afhi->techinfo? afhi->techinfo : "",
-               status_item_list[SI_ARTIST], afhi->tags.artist? afhi->tags.artist : "",
-               status_item_list[SI_TITLE], afhi->tags.title? afhi->tags.title : "",
-               status_item_list[SI_YEAR], afhi->tags.year? afhi->tags.year : "",
-               status_item_list[SI_ALBUM], afhi->tags.album? afhi->tags.album : "",
-               status_item_list[SI_COMMENT], afhi->tags.comment? afhi->tags.comment : ""
+               status_item_list[SI_num_chunks], afhi->chunks_total,
+               status_item_list[SI_max_chunk_size], afhi->max_chunk_size,
+               status_item_list[SI_techinfo], afhi->techinfo? afhi->techinfo : "",
+               status_item_list[SI_artist], afhi->tags.artist? afhi->tags.artist : "",
+               status_item_list[SI_title], afhi->tags.title? afhi->tags.title : "",
+               status_item_list[SI_year], afhi->tags.year? afhi->tags.year : "",
+               status_item_list[SI_album], afhi->tags.album? afhi->tags.album : "",
+               status_item_list[SI_comment], afhi->tags.comment? afhi->tags.comment : ""
        );
 }
 
+/**
+ * Determine the maximal chunk size by investigating the chunk table.
+ *
+ * \param afhi Value/result.
+ *
+ * This function iterates over the chunk table and sets ->max_chunk_size
+ * accordingly. The function exists only for backward compatibility since as of
+ * version 0.6.0, para_server stores the maximal chunk size in its database.
+ * This function is only called if the database value is zero, indicating that
+ * the file was added by an older server version.
+ */
+void set_max_chunk_size(struct afh_info *afhi)
+{
+       uint32_t n, max = 0, old = 0;
+
+       for (n = 0; n <= afhi->chunks_total; n++) {
+               uint32_t val = afhi->chunk_table[n];
+               /*
+                * If the first chunk is the header, do not consider it for the
+                * calculation of the largest chunk size.
+                */
+               if (n == 0 || (n == 1 && afhi->header_len > 0)) {
+                       old = val;
+                       continue;
+               }
+               max = PARA_MAX(max, val - old);
+               old = val;
+       }
+       afhi->max_chunk_size = max;
+}
+
 /**
  * Create a copy of the given file with altered meta tags.
  *
@@ -418,7 +476,7 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re
 int afh_rewrite_tags(int audio_format_id, void *map, size_t mapsize,
                struct taginfo *tags, int output_fd, const char *filename)
 {
-       struct audio_format_handler *afh = afl + audio_format_id;
+       struct audio_format_handler *afh = afl[audio_format_id];
 
        if (!afh->rewrite_tags)
                return -ERRNO_TO_PARA_ERROR(ENOTSUP);
index 28d8f3980f814763cfe249e5762203ff5038a9a5..4f8ff4974f018ef92e5055ac7a28bf337e3aa301 100644 (file)
@@ -1,22 +1,18 @@
-/*
- * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afh_recv.c Receiver for streaming local files. */
 
 #include <regex.h>
 #include <sys/types.h>
+#include <lopsub.h>
 
+#include "recv_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "recv.h"
-#include "afh_recv.cmdline.h"
 #include "string.h"
 #include "fd.h"
 #include "afh.h"
@@ -31,6 +27,7 @@ struct private_afh_recv_data {
        long unsigned last_chunk;
        struct timeval stream_start;
        uint32_t current_chunk;
+       void *afh_context;
 };
 
 static int afh_execute(struct btr_node *btrn, const char *cmd, char **result)
@@ -58,80 +55,71 @@ static int afh_execute(struct btr_node *btrn, const char *cmd, char **result)
                        return ret;
                if (x >= pard->afhi.chunks_total)
                        return -ERRNO_TO_PARA_ERROR(EINVAL);
-               pard->first_chunk = afh_get_start_chunk(x, &pard->afhi);
+               pard->first_chunk = afh_get_start_chunk(x, &pard->afhi,
+                       pard->audio_format_num);
                pard->current_chunk = pard->first_chunk;
                return 1;
        }
        return -E_BTR_NAVAIL;
 }
 
-static void *afh_recv_parse_config(int argc, char **argv)
-{
-       struct afh_recv_args_info *tmp = para_calloc(sizeof(*tmp));
-
-       afh_recv_cmdline_parser(argc, argv, tmp);
-       return tmp;
-}
-
-static void afh_recv_free_config(void *conf)
-{
-       if (!conf)
-               return;
-       afh_recv_cmdline_parser_free(conf);
-       free(conf);
-}
-
 static int afh_recv_open(struct receiver_node *rn)
 {
-       struct afh_recv_args_info *conf = rn->conf;
+       struct lls_parse_result *lpr = rn->lpr;
        struct private_afh_recv_data *pard;
        struct afh_info *afhi;
-       char *filename = conf->filename_arg;
-
+       const char *fn = RECV_CMD_OPT_STRING_VAL(AFH, FILENAME, lpr), *msg;
+       int32_t bc = RECV_CMD_OPT_INT32_VAL(AFH, BEGIN_CHUNK, lpr);
+       const struct lls_opt_result *r_e = RECV_CMD_OPT_RESULT(AFH, END_CHUNK, lpr);
        int ret;
 
-       if (!filename || *filename == '\0')
+       if (!fn || *fn == '\0')
                return -E_AFH_RECV_BAD_FILENAME;
        rn->private_data = pard = para_calloc(sizeof(*pard));
        afhi = &pard->afhi;
-       ret = mmap_full_file(filename, O_RDONLY, &pard->map,
+       ret = mmap_full_file(fn, O_RDONLY, &pard->map,
                &pard->map_size, &pard->fd);
        if (ret < 0)
                goto out;
-       ret = compute_afhi(filename, pard->map, pard->map_size,
+       ret = compute_afhi(fn, pard->map, pard->map_size,
                pard->fd, afhi);
        if (ret < 0)
                goto out_unmap;
        pard->audio_format_num = ret;
        ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+       msg = "no data chunks";
        if (afhi->chunks_total == 0)
                goto out_clear_afhi;
-       if (PARA_ABS(conf->begin_chunk_arg) >= afhi->chunks_total)
+       msg = "invalid begin chunk";
+       if (PARA_ABS(bc) >= afhi->chunks_total)
                goto out_clear_afhi;
-       if (conf->begin_chunk_arg >= 0)
-               pard->first_chunk = afh_get_start_chunk(
-                       conf->begin_chunk_arg, &pard->afhi);
+       if (bc >= 0)
+               pard->first_chunk = afh_get_start_chunk(bc, &pard->afhi,
+                       pard->audio_format_num);
        else
-               pard->first_chunk = afh_get_start_chunk(
-                       afhi->chunks_total + conf->begin_chunk_arg,
-                       &pard->afhi);
-       if (conf->end_chunk_given) {
+               pard->first_chunk = afh_get_start_chunk(afhi->chunks_total + bc,
+                       &pard->afhi, pard->audio_format_num);
+       if (lls_opt_given(r_e)) {
+               int32_t ec = lls_int32_val(0, r_e);
                ret = -ERRNO_TO_PARA_ERROR(EINVAL);
-               if (PARA_ABS(conf->end_chunk_arg) > afhi->chunks_total)
+               msg = "invalid end chunk";
+               if (PARA_ABS(ec) > afhi->chunks_total)
                        goto out_clear_afhi;
-               if (conf->end_chunk_arg >= 0)
-                       pard->last_chunk = conf->end_chunk_arg;
+               if (ec >= 0)
+                       pard->last_chunk = ec;
                else
-                       pard->last_chunk = afhi->chunks_total + conf->end_chunk_arg;
+                       pard->last_chunk = afhi->chunks_total + ec;
        } else
                pard->last_chunk = afhi->chunks_total - 1;
        ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+       msg = "begin chunk >= end chunk!?";
        if (pard->first_chunk >= pard->last_chunk)
                goto out_clear_afhi;
        pard->current_chunk = pard->first_chunk;
        return pard->audio_format_num;
 out_clear_afhi:
        clear_afhi(afhi);
+       PARA_ERROR_LOG("%s: %s\n", fn, msg);
 out_unmap:
        para_munmap(pard->map, pard->map_size);
        close(pard->fd);
@@ -150,6 +138,7 @@ static void afh_recv_close(struct receiver_node *rn)
        clear_afhi(&pard->afhi);
        para_munmap(pard->map, pard->map_size);
        close(pard->fd);
+       afh_close(pard->afh_context, pard->audio_format_num);
        freep(&rn->private_data);
 }
 
@@ -158,13 +147,14 @@ static void afh_recv_pre_select(struct sched *s, void *context)
        struct receiver_node *rn = context;
        struct private_afh_recv_data *pard = rn->private_data;
        struct afh_info *afhi = &pard->afhi;
-       struct afh_recv_args_info *conf = rn->conf;
+       struct lls_parse_result *lpr = rn->lpr;
        struct timeval chunk_time;
        int state = generic_recv_pre_select(s, rn);
+       unsigned j_given = RECV_CMD_OPT_GIVEN(AFH, JUST_IN_TIME, lpr);
 
        if (state <= 0)
                return;
-       if (!conf->just_in_time_given) {
+       if (!j_given) {
                sched_min_delay(s);
                return;
        }
@@ -176,20 +166,22 @@ static void afh_recv_pre_select(struct sched *s, void *context)
 static int afh_recv_post_select(__a_unused struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
-       struct afh_recv_args_info *conf = rn->conf;
+       struct lls_parse_result *lpr = rn->lpr;
        struct private_afh_recv_data *pard = rn->private_data;
        struct btr_node *btrn = rn->btrn;
        struct afh_info *afhi = &pard->afhi;
        int ret;
        char *buf;
-       const char *start, *end;
+       const char *start;
        size_t size;
        struct timeval chunk_time;
+       unsigned j_given = RECV_CMD_OPT_GIVEN(AFH, JUST_IN_TIME, lpr);
+       unsigned H_given = RECV_CMD_OPT_GIVEN(AFH, NO_HEADER, lpr);
 
        ret = btr_node_status(btrn, 0, BTR_NT_ROOT);
        if (ret <= 0)
                goto out;
-       if (pard->first_chunk > 0 && !conf->no_header_given) {
+       if (pard->first_chunk > 0 && !H_given) {
                char *header;
                afh_get_header(afhi, pard->audio_format_num, pard->map,
                        pard->map_size, &header, &size);
@@ -201,12 +193,17 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
                        afh_free_header(header, pard->audio_format_num);
                }
        }
-       if (!conf->just_in_time_given) {
-               afh_get_chunk(pard->first_chunk, afhi, pard->map, &start, &size);
-               afh_get_chunk(pard->last_chunk, afhi, pard->map, &end, &size);
-               end += size;
-               PARA_INFO_LOG("adding %td bytes\n", end - start);
-               btr_add_output_dont_free(start, end - start, btrn);
+       if (!j_given) {
+               long unsigned n;
+               for (n = pard->first_chunk; n < pard->last_chunk; n++) {
+                       ret = afh_get_chunk(n, afhi, pard->audio_format_num,
+                               pard->map, pard->map_size, &start, &size,
+                               &pard->afh_context);
+                       if (ret < 0)
+                               goto out;
+                       PARA_DEBUG_LOG("adding %zu bytes\n", size);
+                       btr_add_output_dont_free(start, size, btrn);
+               }
                ret = -E_RECV_EOF;
                goto out;
        }
@@ -219,7 +216,12 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
                if (ret > 0)
                        goto out;
        }
-       afh_get_chunk(pard->current_chunk, afhi, pard->map, &start, &size);
+       ret = afh_get_chunk(pard->current_chunk, afhi,
+               pard->audio_format_num, pard->map,
+               pard->map_size, &start, &size,
+               &pard->afh_context);
+       if (ret < 0)
+               goto out;
        PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk);
        btr_add_output_dont_free(start, size, btrn);
        if (pard->current_chunk >= pard->last_chunk) {
@@ -236,26 +238,10 @@ out:
        return ret;
 }
 
-/**
- * The init function of the afh receiver.
- *
- * \param r Pointer to the receiver struct to initialize.
- *
- * This initializes all function pointers of \a r.
- */
-void afh_recv_init(struct receiver *r)
-{
-       struct afh_recv_args_info dummy;
-
-       afh_init();
-       afh_recv_cmdline_parser_init(&dummy);
-       r->open = afh_recv_open;
-       r->close = afh_recv_close;
-       r->pre_select = afh_recv_pre_select;
-       r->post_select = afh_recv_post_select;
-       r->parse_config = afh_recv_parse_config;
-       r->free_config = afh_recv_free_config;
-       r->execute = afh_execute;
-       r->help = (struct ggo_help)DEFINE_GGO_HELP(afh_recv);
-       afh_recv_cmdline_parser_free(&dummy);
-}
+const struct receiver lsg_recv_cmd_com_afh_user_data = {
+       .open = afh_recv_open,
+       .close = afh_recv_close,
+       .pre_select = afh_recv_pre_select,
+       .post_select = afh_recv_post_select,
+       .execute = afh_execute,
+};
diff --git a/afs.c b/afs.c
index 0946b6df3b0766f82f95f0cd100f039753543350..b6cce36f42e30cd5672d7e5cc9934cc2d659ca8f 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afs.c Paraslash's audio file selector. */
 
 #include <signal.h>
 #include <fnmatch.h>
 #include <osl.h>
+#include <lopsub.h>
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
-#include "server.cmdline.h"
+#include "server.lsg.h"
+#include "server_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "crypt.h"
 #include "string.h"
 #include "afh.h"
 #include "afs.h"
-#include "server.h"
 #include "net.h"
+#include "server.h"
 #include "ipc.h"
 #include "list.h"
 #include "sched.h"
 #include "sideband.h"
 #include "command.h"
 
-/** The osl tables used by afs. \sa blob.c. */
+/** The osl tables used by afs. \sa \ref blob.c. */
 enum afs_table_num {
-       /** Contains audio file information. See aft.c. */
+       /** Contains audio file information. See \ref aft.c. */
        TBLNUM_AUDIO_FILES,
-       /** The table for the paraslash attributes. See attribute.c. */
+       /** The table for the paraslash attributes. See \ref attribute.c. */
        TBLNUM_ATTRIBUTES,
        /**
         * Paraslash's scoring system is based on Gaussian normal
         * distributions, and the relevant data is stored in the rbtrees of an
-        * osl table containing only volatile columns.  See score.c for
+        * osl table containing only volatile columns. See \ref score.c for
         * details.
         */
        TBLNUM_SCORES,
        /**
         * A standard blob table containing the mood definitions. For details
-        * see mood.c.
+        * see \ref mood.c.
         */
        TBLNUM_MOODS,
        /** A blob table containing lyrics on a per-song basis. */
@@ -75,11 +74,6 @@ static struct afs_table afs_tables[NUM_AFS_TABLES] = {
 struct command_task {
        /** The file descriptor for the local socket. */
        int fd;
-       /**
-        * Value sent by the command handlers to identify themselves as
-        * children of the running para_server.
-        */
-       uint32_t cookie;
        /** The associated task structure. */
        struct task *task;
 };
@@ -94,15 +88,6 @@ static struct signal_task *signal_task;
 static enum play_mode current_play_mode;
 static char *current_mop; /* mode or playlist specifier. NULL means dummy mood */
 
-/**
- * A random number used to "authenticate" the connection.
- *
- * para_server picks this number by random before it forks the afs process. The
- * command handlers know this number as well and write it to the afs socket,
- * together with the id of the shared memory area which contains the payload of
- * the afs command. A local process has to know this number to abuse the afs
- * service provided by the local socket.
- */
 extern uint32_t afs_socket_cookie;
 
 /**
@@ -127,7 +112,7 @@ extern uint32_t afs_socket_cookie;
  * command socket, so that the handler process can read the id, attach the
  * shared memory area and use the result.
  *
- * \sa struct callback_result.
+ * \sa \ref struct callback_result.
  */
 struct callback_query {
        /** The function to be called. */
@@ -143,7 +128,7 @@ struct callback_query {
  * into the shared memory area holding the result, mainly to let the command
  * handler know the size of the result.
  *
- * \sa struct callback_query.
+ * \sa \ref struct callback_query.
  */
 struct callback_result {
        /** The number of bytes of the result. */
@@ -198,9 +183,8 @@ static int dispatch_result(int result_shmid, callback_result_handler *handler,
  * shmid are passed to that function as an osl object. The private_result_data
  * pointer is passed as the second argument to \a result_handler.
  *
- * \return Number of shared memory areas dispatched on success, negative on errors.
- *
- * \sa send_option_arg_callback_request(), send_standard_callback_request().
+ * \return Number of shared memory areas dispatched on success, negative on
+ * errors.
  */
 int send_callback_request(afs_callback *f, struct osl_object *query,
                callback_result_handler *result_handler,
@@ -235,7 +219,7 @@ int send_callback_request(afs_callback *f, struct osl_object *query,
        *(uint32_t *)buf = afs_socket_cookie;
        *(int *)(buf + sizeof(afs_socket_cookie)) = query_shmid;
 
-       ret = connect_local_socket(conf.afs_socket_arg);
+       ret = connect_local_socket(OPT_STRING_VAL(AFS_SOCKET));
        if (ret < 0)
                goto out;
        fd = ret;
@@ -280,73 +264,33 @@ out:
 }
 
 /**
- * Send a callback request passing an options structure and an argument vector.
+ * Wrapper for send_callback_request() which passes a lopsub parse result.
  *
- * \param options pointer to an arbitrary data structure.
- * \param argc Argument count.
- * \param argv Standard argument vector.
  * \param f The callback function.
- * \param result_handler See \ref send_callback_request.
- * \param private_result_data See \ref send_callback_request.
+ * \param cmd Needed for (de-)serialization.
+ * \param lpr Must match cmd.
+ * \param private_result_data Passed to send_callback_request().
  *
- * Some command handlers pass command-specific options to a callback, together
- * with a list of further arguments (often a list of audio files). This
- * function allows to pass an arbitrary structure (given as an osl object) and
- * a usual argument vector to the specified callback.
+ * This function serializes the parse result given by the lpr pointer into a
+ * buffer. The buffer is sent as the query to the afs process with the callback
+ * mechanism.
  *
- * \return The return value of the underlying call to \ref
- * send_callback_request().
- *
- * \sa send_standard_callback_request(), send_callback_request().
+ * \return The return value of the underlying call to send_callback_request().
  */
-int send_option_arg_callback_request(struct osl_object *options,
-               int argc,  char * const * const argv, afs_callback *f,
-               callback_result_handler *result_handler,
-               void *private_result_data)
+int send_lls_callback_request(afs_callback *f,
+               const struct lls_command * const cmd,
+               struct lls_parse_result *lpr, void *private_result_data)
 {
-       char *p;
-       int i, ret;
-       struct osl_object query = {.size = options? options->size : 0};
-
-       for (i = 0; i < argc; i++)
-               query.size += strlen(argv[i]) + 1;
-       query.data = para_malloc(query.size);
-       p = query.data;
-       if (options) {
-               memcpy(query.data, options->data, options->size);
-               p += options->size;
-       }
-       for (i = 0; i < argc; i++) {
-               strcpy(p, argv[i]); /* OK */
-               p += strlen(argv[i]) + 1;
-       }
-       ret = send_callback_request(f, &query, result_handler,
-               private_result_data);
-       free(query.data);
-       return ret;
-}
+       struct osl_object query;
+       char *buf = NULL;
+       int ret = lls_serialize_parse_result(lpr, cmd, &buf, &query.size);
 
-/**
- * Send a callback request with an argument vector only.
- *
- * \param argc The same meaning as in send_option_arg_callback_request().
- * \param argv The same meaning as in send_option_arg_callback_request().
- * \param f The same meaning as in send_option_arg_callback_request().
- * \param result_handler See \ref send_callback_request.
- * \param private_result_data See \ref send_callback_request.
- *
- * This is similar to send_option_arg_callback_request(), but no options buffer
- * is passed to the parent process.
- *
- * \return The return value of the underlying call to
- * send_option_arg_callback_request().
- */
-int send_standard_callback_request(int argc,  char * const * const argv,
-               afs_callback *f, callback_result_handler *result_handler,
-               void *private_result_data)
-{
-       return send_option_arg_callback_request(NULL, argc, argv, f, result_handler,
+       assert(ret >= 0);
+       query.data = buf;
+       ret = send_callback_request(f, &query, afs_cb_result_handler,
                private_result_data);
+       free(buf);
+       return ret;
 }
 
 static int action_if_pattern_matches(struct osl_row *row, void *data)
@@ -354,30 +298,37 @@ static int action_if_pattern_matches(struct osl_row *row, void *data)
        struct pattern_match_data *pmd = data;
        struct osl_object name_obj;
        const char *p, *name;
-       int ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num, &name_obj));
-       const char *pattern_txt = (const char *)pmd->patterns.data;
+       int i, ret;
 
+       ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num,
+               &name_obj));
        if (ret < 0)
                return ret;
        name = (char *)name_obj.data;
        if ((!name || !*name) && (pmd->pm_flags & PM_SKIP_EMPTY_NAME))
                return 1;
-       if (pmd->patterns.size == 0 &&
-                       (pmd->pm_flags & PM_NO_PATTERN_MATCHES_EVERYTHING)) {
-               pmd->num_matches++;
-               return pmd->action(pmd->table, row, name, pmd->data);
+       if (lls_num_inputs(pmd->lpr) == 0) {
+               if (pmd->pm_flags & PM_NO_PATTERN_MATCHES_EVERYTHING) {
+                       pmd->num_matches++;
+                       return pmd->action(pmd->table, row, name, pmd->data);
+               }
        }
-       for (p = pattern_txt; p < pattern_txt + pmd->patterns.size;
-                       p += strlen(p) + 1) {
+       i = pmd->input_skip;
+       for (;;) {
+               if (i >= lls_num_inputs(pmd->lpr))
+                       break;
+               p = lls_input(i, pmd->lpr);
                ret = fnmatch(p, name, pmd->fnmatch_flags);
-               if (ret == FNM_NOMATCH)
-                       continue;
-               if (ret)
-                       return -E_FNMATCH;
-               ret = pmd->action(pmd->table, row, name, pmd->data);
-               if (ret >= 0)
-                       pmd->num_matches++;
-               return ret;
+               if (ret != FNM_NOMATCH) {
+                       if (ret != 0)
+                               return -E_FNMATCH;
+                       ret = pmd->action(pmd->table, row, name, pmd->data);
+                       if (ret >= 0)
+                               pmd->num_matches++;
+                       return ret;
+
+               }
+               i++;
        }
        return 1;
 }
@@ -411,12 +362,12 @@ int for_each_matching_row(struct pattern_match_data *pmd)
  * \a obj1 is found, respectively, to be less than, to match, or be greater than
  * obj2.
  *
- * \sa strcmp(3), strncmp(3), osl_compare_func.
+ * \sa strcmp(3), strncmp(3).
  */
 int string_compare(const struct osl_object *obj1, const struct osl_object *obj2)
 {
-       const char *str1 = (const char *)obj1->data;
-       const char *str2 = (const char *)obj2->data;
+       const char *str1 = obj1->data;
+       const char *str2 = obj2->data;
        return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size));
 }
 
@@ -463,17 +414,17 @@ static int pass_afd(int fd, char *buf, size_t size)
  *
  * \return Standard.
  *
- * \sa open_and_update_audio_file().
+ * \sa \ref open_and_update_audio_file().
  */
 static int open_next_audio_file(void)
 {
-       struct audio_file_data afd;
-       int ret, shmid;
+       int ret, shmid, fd;
        char buf[8];
 
-       ret = open_and_update_audio_file(&afd);
+       ret = open_and_update_audio_file(&fd);
        if (ret < 0) {
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               if (ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
+                       PARA_ERROR_LOG("%s\n", para_strerror(-ret));
                goto no_admissible_files;
        }
        shmid = ret;
@@ -483,8 +434,8 @@ static int open_next_audio_file(void)
        }
        *(uint32_t *)buf = NEXT_AUDIO_FILE;
        *(uint32_t *)(buf + 4) = (uint32_t)shmid;
-       ret = pass_afd(afd.fd, buf, 8);
-       close(afd.fd);
+       ret = pass_afd(fd, buf, 8);
+       close(fd);
        if (ret >= 0)
                return ret;
 destroy:
@@ -497,23 +448,30 @@ no_admissible_files:
 }
 
 /* Never fails if arg == NULL */
-static int activate_mood_or_playlist(const char *arg, int *num_admissible)
+static int activate_mood_or_playlist(const char *arg, int *num_admissible,
+               char **errmsg)
 {
        enum play_mode mode;
        int ret;
 
        if (!arg) {
-               ret = change_current_mood(NULL); /* always successful */
+               ret = change_current_mood(NULL, NULL); /* always successful */
                mode = PLAY_MODE_MOOD;
        } else {
                if (!strncmp(arg, "p/", 2)) {
                        ret = playlist_open(arg + 2);
+                       if (ret < 0 && errmsg)
+                               *errmsg = make_message( "could not open %s",
+                                       arg);
                        mode = PLAY_MODE_PLAYLIST;
                } else if (!strncmp(arg, "m/", 2)) {
-                       ret = change_current_mood(arg + 2);
+                       ret = change_current_mood(arg + 2, errmsg);
                        mode = PLAY_MODE_MOOD;
-               } else
-                       return -E_AFS_SYNTAX;
+               } else {
+                       if (errmsg)
+                               *errmsg = make_message("%s: parse error", arg);
+                       return -ERRNO_TO_PARA_ERROR(EINVAL);
+               }
                if (ret < 0)
                        return ret;
        }
@@ -528,10 +486,12 @@ static int activate_mood_or_playlist(const char *arg, int *num_admissible)
                        strncpy(mmd->afs_mode_string, arg,
                                sizeof(mmd->afs_mode_string));
                        mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0';
+                       mmd->events++;
                        mutex_unlock(mmd_mutex);
                } else {
                        mutex_lock(mmd_mutex);
                        strcpy(mmd->afs_mode_string, "dummy");
+                       mmd->events++;
                        mutex_unlock(mmd_mutex);
                        current_mop = NULL;
                }
@@ -590,74 +550,88 @@ static void flush_and_free_pb(struct para_buffer *pb)
 
 static int com_select_callback(struct afs_callback_arg *aca)
 {
-       const char *arg = aca->query.data;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+       const char *arg;
        int num_admissible, ret;
+       char *errmsg;
 
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       arg = lls_input(0, aca->lpr);
        ret = clear_score_table();
        if (ret < 0) {
-               para_printf(&aca->pbout, "could not clear score table: %s\n",
-                       para_strerror(-ret));
-               return ret;
+               para_printf(&aca->pbout, "could not clear score table\n");
+               goto free_lpr;
        }
        if (current_play_mode == PLAY_MODE_MOOD)
                close_current_mood();
        else
                playlist_close();
-       ret = activate_mood_or_playlist(arg, &num_admissible);
+       ret = activate_mood_or_playlist(arg, &num_admissible, &errmsg);
        if (ret >= 0)
                goto out;
        /* ignore subsequent errors (but log them) */
+       para_printf(&aca->pbout, "%s\n", errmsg);
+       free(errmsg);
        para_printf(&aca->pbout, "could not activate %s\n", arg);
-       if (current_mop) {
+       if (current_mop && strcmp(current_mop, arg) != 0) {
                int ret2;
                para_printf(&aca->pbout, "switching back to %s\n", current_mop);
-               ret2 = activate_mood_or_playlist(current_mop, &num_admissible);
+               ret2 = activate_mood_or_playlist(current_mop, &num_admissible,
+                       &errmsg);
                if (ret2 >= 0)
                        goto out;
+               para_printf(&aca->pbout, "%s\n", errmsg);
+               free(errmsg);
                para_printf(&aca->pbout, "could not reactivate %s: %s\n",
                        current_mop, para_strerror(-ret2));
        }
        para_printf(&aca->pbout, "activating dummy mood\n");
-       activate_mood_or_playlist(NULL, &num_admissible);
+       activate_mood_or_playlist(NULL, &num_admissible, NULL);
 out:
-       para_printf(&aca->pbout, "activated %s (%d admissible files)\n",
-               current_mop? current_mop : "dummy mood", num_admissible);
+       para_printf(&aca->pbout, "activated %s (%d admissible file%s)\n",
+               current_mop? current_mop : "dummy mood", num_admissible,
+                       num_admissible == 1? "" : "s");
+free_lpr:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-int com_select(struct command_context *cc)
+static int com_select(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       struct osl_object query;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
 
-       if (cc->argc != 2)
-               return -E_AFS_SYNTAX;
-       query.data = cc->argv[1];
-       query.size = strlen(cc->argv[1]) + 1;
-       return send_callback_request(com_select_callback, &query,
-               &afs_cb_result_handler, cc);
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       return send_lls_callback_request(com_select_callback, cmd, lpr, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(select);
 
-static void init_admissible_files(char *arg)
+static void init_admissible_files(const char *arg)
 {
-       if (activate_mood_or_playlist(arg, NULL) < 0)
-               activate_mood_or_playlist(NULL, NULL); /* always successful */
+       int ret = activate_mood_or_playlist(arg, NULL, NULL);
+       if (ret < 0) {
+               assert(arg);
+               PARA_WARNING_LOG("could not activate %s: %s\n", arg,
+                       para_strerror(-ret));
+               activate_mood_or_playlist(NULL, NULL, NULL);
+       }
 }
 
 static int setup_command_socket_or_die(void)
 {
        int ret, socket_fd;
-       char *socket_name = conf.afs_socket_arg;
+       const char *socket_name = OPT_STRING_VAL(AFS_SOCKET);
 
        unlink(socket_name);
-       ret = create_local_socket(socket_name, 0);
+       ret = create_local_socket(socket_name);
        if (ret < 0) {
-               ret = create_local_socket(socket_name,
-                       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IROTH);
-               if (ret < 0) {
-                       PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret),
-                               socket_name);
-                       exit(EXIT_FAILURE);
-               }
+               PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret), socket_name);
+               exit(EXIT_FAILURE);
        }
        socket_fd = ret;
        PARA_INFO_LOG("listening on socket %s (fd %d)\n", socket_name,
@@ -665,21 +639,23 @@ static int setup_command_socket_or_die(void)
        return socket_fd;
 }
 
+static char *database_dir;
+
 static void close_afs_tables(void)
 {
        int i;
        PARA_NOTICE_LOG("closing afs_tables\n");
        for (i = 0; i < NUM_AFS_TABLES; i++)
                afs_tables[i].close();
+       free(database_dir);
+       database_dir = NULL;
 }
 
-static char *database_dir;
-
 static void get_database_dir(void)
 {
        if (!database_dir) {
-               if (conf.afs_database_dir_given)
-                       database_dir = para_strdup(conf.afs_database_dir_arg);
+               if (OPT_GIVEN(AFS_DATABASE_DIR))
+                       database_dir = para_strdup(OPT_STRING_VAL(AFS_DATABASE_DIR));
                else {
                        char *home = para_homedir();
                        database_dir = make_message(
@@ -712,8 +688,7 @@ static int open_afs_tables(void)
                ret = afs_tables[i].open(database_dir);
                if (ret >= 0)
                        continue;
-               PARA_ERROR_LOG("%s init: %s\n", afs_tables[i].name,
-                       para_strerror(-ret));
+               PARA_ERROR_LOG("could not open %s\n", afs_tables[i].name);
                break;
        }
        if (ret >= 0)
@@ -888,12 +863,12 @@ static int execute_server_command(fd_set *rfds)
                return ret;
        buf[n] = '\0';
        if (strcmp(buf, "new"))
-               return -E_BAD_CMD;
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
        return open_next_audio_file();
 }
 
 /* returns 0 if no data available, 1 else */
-static int execute_afs_command(int fd, fd_set *rfds, uint32_t expected_cookie)
+static int execute_afs_command(int fd, fd_set *rfds)
 {
        uint32_t cookie;
        int query_shmid;
@@ -911,9 +886,9 @@ static int execute_afs_command(int fd, fd_set *rfds, uint32_t expected_cookie)
                return 1;
        }
        cookie = *(uint32_t *)buf;
-       if (cookie != expected_cookie) {
+       if (cookie != afs_socket_cookie) {
                PARA_NOTICE_LOG("received invalid cookie (got %u, expected %u)\n",
-                       (unsigned)cookie, (unsigned)expected_cookie);
+                       (unsigned)cookie, (unsigned)afs_socket_cookie);
                return 1;
        }
        query_shmid = *(int *)(buf + sizeof(cookie));
@@ -951,7 +926,7 @@ static int command_post_select(struct sched *s, void *context)
        }
        /* Check the list of connected clients. */
        list_for_each_entry_safe(client, tmp, &afs_client_list, node) {
-               ret = execute_afs_command(client->fd, &s->rfds, ct->cookie);
+               ret = execute_afs_command(client->fd, &s->rfds);
                if (ret == 0) { /* prevent bogus connection flooding */
                        struct timeval diff;
                        tv_diff(now, &client->connect_time, &diff);
@@ -982,11 +957,10 @@ static int command_post_select(struct sched *s, void *context)
        return 0;
 }
 
-static void register_command_task(uint32_t cookie, struct sched *s)
+static void register_command_task(struct sched *s)
 {
        struct command_task *ct = &command_task_struct;
        ct->fd = setup_command_socket_or_die();
-       ct->cookie = cookie;
 
        ct->task = task_register(&(struct task_info) {
                .name = "afs command",
@@ -999,10 +973,9 @@ static void register_command_task(uint32_t cookie, struct sched *s)
 /**
  * Initialize the audio file selector process.
  *
- * \param cookie The value used for "authentication".
  * \param socket_fd File descriptor used for communication with the server.
  */
-__noreturn void afs_init(uint32_t cookie, int socket_fd)
+__noreturn void afs_init(int socket_fd)
 {
        static struct sched s;
        int i, ret;
@@ -1018,10 +991,9 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd)
        ret = mark_fd_nonblocking(server_socket);
        if (ret < 0)
                goto out_close;
-       PARA_INFO_LOG("server_socket: %d, afs_socket_cookie: %u\n",
-               server_socket, (unsigned) cookie);
-       init_admissible_files(conf.afs_initial_mode_arg);
-       register_command_task(cookie, &s);
+       PARA_INFO_LOG("server_socket: %d\n", server_socket);
+       init_admissible_files(OPT_STRING_VAL(AFS_INITIAL_MODE));
+       register_command_task(&s);
        s.default_timeout.tv_sec = 0;
        s.default_timeout.tv_usec = 999 * 1000;
        ret = write(socket_fd, "\0", 1);
@@ -1033,9 +1005,14 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd)
        }
        ret = schedule(&s);
        sched_shutdown(&s);
+       close_current_mood();
 out_close:
        close_afs_tables();
 out:
+       signal_shutdown(signal_task);
+       free_status_items();
+       free(current_mop);
+       free_lpr();
        if (ret < 0)
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        exit(EXIT_FAILURE);
@@ -1047,6 +1024,7 @@ static int com_init_callback(struct afs_callback_arg *aca)
        int i, ret;
 
        close_afs_tables();
+       get_database_dir();
        for (i = 0; i < NUM_AFS_TABLES; i++) {
                struct afs_table *t = &afs_tables[i];
 
@@ -1065,28 +1043,30 @@ static int com_init_callback(struct afs_callback_arg *aca)
        }
        ret = open_afs_tables();
        if (ret < 0)
-               para_printf(&aca->pbout, "cannot open afs tables\n");
+               para_printf(&aca->pbout, "cannot open afs tables: %s\n",
+                       para_strerror(-ret));
 out:
        return ret;
 }
 
-int com_init(struct command_context *cc)
+static int com_init(struct command_context *cc, struct lls_parse_result *lpr)
 {
        int i, j, ret;
        uint32_t table_mask = (1 << (NUM_AFS_TABLES + 1)) - 1;
        struct osl_object query = {.data = &table_mask,
                .size = sizeof(table_mask)};
+       unsigned num_inputs = lls_num_inputs(lpr);
 
        ret = make_database_dir();
        if (ret < 0)
                return ret;
-       if (cc->argc != 1) {
+       if (num_inputs > 0) {
                table_mask = 0;
-               for (i = 1; i < cc->argc; i++) {
+               for (i = 0; i < num_inputs; i++) {
                        for (j = 0; j < NUM_AFS_TABLES; j++) {
                                struct afs_table *t = &afs_tables[j];
 
-                               if (strcmp(cc->argv[i], t->name))
+                               if (strcmp(lls_input(i, lpr), t->name))
                                        continue;
                                table_mask |= (1 << j);
                                break;
@@ -1098,77 +1078,37 @@ int com_init(struct command_context *cc)
        return send_callback_request(com_init_callback, &query,
                afs_cb_result_handler, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(init);
 
-/**
- * Flags for the check command.
- *
- * \sa com_check().
- */
-enum com_check_flags {
-       /** Check the audio file table. */
-       CHECK_AFT = 1,
-       /** Check the mood table. */
-       CHECK_MOODS = 2,
-       /** Check the playlist table. */
-       CHECK_PLAYLISTS = 4,
-       /** Check the attribute table against the audio file table. */
-       CHECK_ATTS = 8
-};
-
-int com_check(struct command_context *cc)
+static int com_check(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       unsigned flags = 0;
-       int i, ret;
+       const struct lls_opt_result *r_a = SERVER_CMD_OPT_RESULT(CHECK, AFT, lpr);
+       const struct lls_opt_result *r_A = SERVER_CMD_OPT_RESULT(CHECK, ATTRIBUTE, lpr);
+       const struct lls_opt_result *r_m = SERVER_CMD_OPT_RESULT(CHECK, MOOD, lpr);
+       const struct lls_opt_result *r_p = SERVER_CMD_OPT_RESULT(CHECK, PLAYLIST, lpr);
+       bool noopt = !lls_opt_given(r_a) && !lls_opt_given(r_A)
+               && !lls_opt_given(r_m) && !lls_opt_given(r_p);
+       int ret;
 
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-a")) {
-                       flags |= CHECK_AFT;
-                       continue;
-               }
-               if (!strcmp(arg, "-A")) {
-                       flags |= CHECK_ATTS;
-                       continue;
-               }
-               if (!strcmp(arg, "-p")) {
-                       flags |= CHECK_PLAYLISTS;
-                       continue;
-               }
-               if (!strcmp(arg, "-m")) {
-                       flags |= CHECK_MOODS;
-                       continue;
-               }
-               return -E_AFS_SYNTAX;
-       }
-       if (i < cc->argc)
-               return -E_AFS_SYNTAX;
-       if (!flags)
-               flags = ~0U;
-       if (flags & CHECK_AFT) {
+       if (noopt || lls_opt_given(r_a)) {
                ret = send_callback_request(aft_check_callback, NULL,
                        afs_cb_result_handler, cc);
                if (ret < 0)
                        return ret;
        }
-       if (flags & CHECK_ATTS) {
+       if (noopt || lls_opt_given(r_A)) {
                ret = send_callback_request(attribute_check_callback, NULL,
                        afs_cb_result_handler, cc);
                if (ret < 0)
                        return ret;
        }
-       if (flags & CHECK_PLAYLISTS) {
+       if (noopt || lls_opt_given(r_p)) {
                ret = send_callback_request(playlist_check_callback,
                        NULL, afs_cb_result_handler, cc);
                if (ret < 0)
                        return ret;
        }
-       if (flags & CHECK_MOODS) {
+       if (noopt || lls_opt_given(r_m)) {
                ret = send_callback_request(mood_check_callback, NULL,
                        afs_cb_result_handler, cc);
                if (ret < 0)
@@ -1176,6 +1116,7 @@ int com_check(struct command_context *cc)
        }
        return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(check);
 
 /**
  * The afs event dispatcher.
diff --git a/afs.cmd b/afs.cmd
deleted file mode 100644 (file)
index 76b5f4d..0000000
--- a/afs.cmd
+++ /dev/null
@@ -1,304 +0,0 @@
-BN: afs
-SF: afs.c aft.c attribute.c
-SN: list of afs commands
-TM: mood lyr img pl
----
-N: add
-P: AFS_READ | AFS_WRITE
-D: Add or update audio files.
-U: add [-a] [-l] [-f] [-v] path...
-H: Each path must be absolute and refer to either an audio file, or a
-H: directory. In case of a directory, all audio files in that directory
-H: are added recursively. Only absolute paths are accepted.
-H:
-H: Options:
-H:
-H: -a  Add all files. The default is to add only files ending in a
-H:     known suffix for a supported audio format.
-H:
-H: -l  Add files lazily. If the path already exists in the
-H:     database, skip this file.  This operation is really cheap. Useful
-H:     to update large directories after some files have been added or
-H:     deleted.
-H:
-H: -f  Force adding/updating. Recompute the audio format handler data
-H:     even if a file with the same path and the same hash value exists.
-H:
-H: -v  Verbose mode. Print what is being done.
----
-N: init
-P: AFS_READ | AFS_WRITE
-D: Initialize the osl tables for the audio file selector.
-U: init [table_name ...]
-H: When invoked without arguments, this command creates all tables. Otherwise
-H: only the tables given by table_name... are created.
----
-N: ls
-P: AFS_READ
-D: List audio files.
-U: ls [-l=mode] [-p] [-a] [-r] [-d] [-s=order] [pattern...]
-H: Print a list of all audio files matching pattern.
-H:
-H: Options:
-H:
-H: -l=mode     Change listing mode. Defaults to short listing if not given.
-H:
-H:    Available modes:
-H:    s: short listing mode
-H:    l: long listing mode (equivalent to -l)
-H:    v: verbose listing mode
-H:    p: parser-friendly mode
-H:    m: mbox listing mode
-H:    c: chunk-table listing mode
-H:
-H: -F  List full paths. If this option is not specified, only the basename
-H:     of each file is printed.
-H: -p  Synonym for -F. Deprecated.
-H:
-H: -b  Print only the basename of each matching file. This is the default, so
-H:     the option is currently a no-op. It is recommended to specify this option,
-H:     though, as the default might change in a future release.
-H:
-H: -a  List only files that are admissible with respect to the current mood or
-H:     playlist.
-H:
-H: -r  Reverse sort order.
-H:
-H: -d  Print dates as seconds after the epoch.
-H:
-H: -s=order
-H:     Change sort order. Defaults to alphabetical path sort if not given.
-H:
-H:   Possible values for order:
-H:   p: by path
-H:   l: by last played time
-H:   s: by score (implies -a)
-H:   n: by num played count
-H:   f: by frequency
-H:   c: by number of channels
-H:   i: by image id
-H:   y: by lyrics id
-H:   b: by bit rate
-H:   d: by duration
-H:   a: by audio format
----
-N: lsatt
-P: AFS_READ
-D: List attributes.
-U: lsatt [-i] [-l] [-r] [pattern]
-H: Print the list of all defined attributes which match the given
-H: pattern. If no pattern is given, the full list is printed.
-H:
-H: Options:
-H:
-H: -i  Sort attributes by id. The default is to sort alphabetically by name.
-H:
-H: -l  Print a long listing containing both identifier and attribute name. The
-H:     default is to print only the name.
-H:
-H: -r  Reverse sort order.
----
-N: setatt
-P: AFS_READ | AFS_WRITE
-D: Set attribute(s) for all files matching a pattern.
-U: setatt attribute{+|-}... pattern
-H: Set ('+') or unset ('-') the given attributes for all audio files matching
-H: pattern.  Example:
-H:
-H:         setatt rock+ punk+ pop- '*foo.mp3'
-H:
-H: sets the 'rock' and the 'punk' attribute and unsets the 'pop'
-H: attribute of all files ending with 'foo.mp3'.
----
-N: addatt
-P: AFS_READ | AFS_WRITE
-D: Add new attribute(s).
-U: addatt attribute1...
-H: This adds new attributes to the attribute table. At most 64
-H: attributes may be defined.
----
-N: mvatt
-P: AFS_READ | AFS_WRITE
-D: Rename an attribute.
-U: mvatt old new
-H: Rename attribute old to new.
----
-N: check
-P: AFS_READ
-D: Run integrity checks against osl tables.
-U: check [-a] [-A] [-m] [-p]
-H: Check the audio file table, the attribute table, the mood definitions
-H: and all defined playlists. Report any inconsistencies.
-H:
-H: Options:
-H:
-H: -a  Run audio file table checks. Checks for entries in the audio file
-H:     table which are not present in the file system. Moreover, it checks
-H:     whether the lyrics id and all entries in the audio file table are
-H:     valid.
-H:
-H: -A  Check the attribute table against the afs attribute bitmask of
-H:     each audio file in the audio file table. Reports audio files
-H:     whose attribute bitmask is invalid, i.e., has a bit set which
-H:     does not correspond to any attribute of the attribute table.
-H:
-H: -m  Run syntax checks on all defined moods in the mood table.
-H:
-H: -p  Check all playlists for lines that correspond to files not contained
-H:     in the audio file table.
-H:
-H: If called without arguments, all checks are run.
----
-N: rmatt
-P: AFS_READ | AFS_WRITE
-D: Remove attribute(s).
-U: rmatt pattern...
-H: Remove all attributes matching any given pattern. All information
-H: about this attribute in the audio file table is lost.
----
-N: rm
-P: AFS_READ | AFS_WRITE
-D: Remove entries from the audio file table.
-U: rm [-v] [-f] [-p] pattern...
-H: Delete all entries in the audio file table that match any given pattern.  Note
-H: that this affects the table entries only; the command won't touch your audio
-H: files on disk.
-H:
-H: Options:
-H:
-H: -v  Verbose mode. Explain what is being done.
-H:
-H: -f  Force mode. Ignore nonexistent files. Don't complain if nothing
-H:     was removed.
-H:
-H: -p  Pathname match. Match a slash in the path only with a slash
-H:     in pattern and not by an asterisk (*) or a question mark
-H:     (?) metacharacter, nor by a bracket expression ([]) containing
-H:     a slash (see fnmatch(3)).
----
-N: touch
-P: AFS_READ | AFS_WRITE
-D: Manipulate the afs entry of audio files.
-U: touch [-n=numplayed] [-l=lastplayed] [-y=lyrics_id] [-i=image_id] [-a=amp] [-v] [-p] pattern
-H: If no option is given, the lastplayed field is set to the current time
-H: and the value of the numplayed field is increased by one. Otherwise,
-H: only the given options are taken into account.
-H:
-H: Options:
-H:
-H: -n  Set the numplayed count, i.e. the number of times this audio
-H:     file was selected for streaming so far.
-H:
-H: -l  Set the lastplayed time, i.e. the last time this audio file was
-H:     selected for streaming. The argument must be a number of seconds
-H:     since the epoch. Example:
-H:
-H:             touch -l=$(date +%s) file
-H:
-H:     sets the lastplayed time of 'file' to the current time.
-H:
-H: -y  Set the lyrics ID which specifies the lyrics data file associated
-H:     with the audio file.
-H:
-H: -i  Like -y, but sets the image ID.
-H:
-H: -a  Set the amplification value (0-255). This determines a scaling
-H:     factor by which the amplitude should be multiplied in order to
-H:     normalize the volume of the audio file.  A value of zero means
-H:     no amplification, 64 means the amplitude should be multiplied
-H:     by a factor of two, 128 by three and so on.
-H:
-H:     This value is used by the amp filter.
-H:
-H: -v  Verbose mode. Explain what is being done.
-H:
-H: -p  Pathname match. Match a slash in the path only with a slash
-H:     in pattern and not by an asterisk (*) or a question mark
-H:     (?) metacharacter, nor by a bracket expression ([]) containing
-H:     a slash (see fnmatch(3)).
----
-N: cpsi
-P: AFS_READ | AFS_WRITE
-D: Copy audio file selector info.
-U: cpsi [-a] [-y] [-i] [-l] [-n] [-v] source pattern...
-H: If no option, or only the -v option is given, all fields of the
-H: audio file selector info are copied to all files matching pattern.
-H: Otherwise, only the given options are taken into account.
-H:
-H: Options:
-H:
-H: -a  Copy attributes.
-H:
-H: -y  Copy the lyrics id.
-H:
-H: -i  Copy the image id.
-H:
-H: -l  Copy the lastplayed time.
-H:
-H: -n  Copy the numplayed count.
-H:
-H: -v  Verbose mode.
----
-N: select
-P: AFS_READ | AFS_WRITE
-D: Activate a mood or a playlist.
-U: select specifier/name
-H: The specifier is either 'm' or 'p' to indicate whether a playlist or
-H: a mood should be activated. Example:
-H:
-H:     select m/foo
-H:
-H: loads the mood named 'foo'.
----
-T: add
-N: add@member@
-O: int com_add@member@(struct command_context *cc);
-P: AFS_READ | AFS_WRITE
-D: Add stdin as a blob to the @member@ table.
-U: add@member@ @member@_name
-H: Read from stdin and ask the audio file selector to create a blob in the
-H: corresponding osl table. If the named blob already exists, it gets replaced
-H: with the new data.
----
-T: cat
-N: cat@member@
-O: int com_cat@member@(struct command_context *cc);
-P: AFS_READ
-D: Dump the contents of a blob of type @member@ to stdout.
-U: cat@member@ @member@_name
-H: Retrieve the named blob and write it to stdout.
----
-T: ls
-N: ls@member@
-O: int com_ls@member@(struct command_context *cc);
-P: AFS_READ
-D: List blobs of type @member@ which match a pattern.
-U: ls@member@ [-i] [-l] [-r] [pattern]
-H: Print the list of all blobs which match the given pattern. If no
-H: pattern is given, the full list is printed.
-H:
-H: Options:
-H:
-H: -i  Sort by identifier. The default is to sort alphabetically by name.
-H:
-H: -l  Print identifier and name. The default is to print only the name.
-H:
-H: -r  Reverse sort order.
----
-T: rm
-N: rm@member@
-O: int com_rm@member@(struct command_context *cc);
-P: AFS_READ | AFS_WRITE
-D: Remove blob(s) of type @member@ from the @member@ table.
-U: rm@member@ pattern...
-H: Remove all blobs whose name matches any of the given patterns.
----
-T: mv
-N: mv@member@
-O: int com_mv@member@(struct command_context *cc);
-P: AFS_READ | AFS_WRITE
-D: Rename a blob of type @member@.
-U: mv@member@ source_@member@_name dest_@member@_name
-H: Rename the blob identified by the source blob name to the destination blob
-H: name. The command fails if the source does not exist, or if the destination
-H: already exists.
diff --git a/afs.h b/afs.h
index 2f9d7e5fc40bdc9c1661336ffce20d7a7df76148..b1606493a05f047afb1e8ecb0fdd0a5bbcbafcfe 100644 (file)
--- a/afs.h
+++ b/afs.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file afs.h Exported symbols of the audio file selector. */
 
@@ -141,12 +137,14 @@ struct pattern_match_data {
        unsigned loop_col_num;
        /** Data from this column is matched against the given patterns. */
        unsigned match_col_num;
-       /** \see pattern_match_flags. */
+       /** \see \ref pattern_match_flags. */
        unsigned pm_flags;
        /** This value is passed verbatim to fnmatch(). */
        int fnmatch_flags;
-       /** Null-terminated array of patterns. */
-       struct osl_object patterns;
+       /** Obtained by deserializing the query buffer in the callback. */
+       struct lls_parse_result *lpr;
+       /** Do not try to match the first inputs of lpr */
+       unsigned input_skip;
        /** Data pointer passed to the action function. */
        void *data;
        /** Gets increased by one for each match. */
@@ -163,6 +161,15 @@ struct afs_callback_arg {
        struct osl_object query;
        /** Will be written on band SBD_OUTPUT, fully buffered. */
        struct para_buffer pbout;
+       /**
+        * Convenience pointer for the deserialized parse result.
+        *
+        * Most afs command handlers call \ref send_lls_callback_request() to
+        * serialize the parse result of the subcommand and pass it to the
+        * callback. In afs context a pointer to the deserialized parse result
+        * is stored here.
+        */
+       struct lls_parse_result *lpr;
 };
 
 /**
@@ -171,16 +178,14 @@ struct afs_callback_arg {
  * Therefore afs commands typically consist of two functions: The command
  * handler and the corresponding callback function that runs in afs context.
  *
- * \sa send_callback_request().
+ * \sa \ref send_callback_request().
  */
 typedef int afs_callback(struct afs_callback_arg *aca);
 
 /**
- * Callbacks send chunks to data back to the command handler. Pointers to
- * this type of function are used by \ref send_callback_request and friends
- * to deal with the data in the command handler process.
- *
- * \sa \ref send_callback_request().
+ * Some AFS callbacks need to send data back to the command handler. Pointers
+ * to this type of function are passed to \ref send_callback_request() and
+ * related functions to dispatch the data in the command handler process.
  */
 typedef int callback_result_handler(struct osl_object *result, uint8_t band, void *private);
 int afs_cb_result_handler(struct osl_object *result, uint8_t band, void *private);
@@ -215,19 +220,15 @@ _static_inline_ int afs_max_size_handler(char *buf, size_t size, void *private)
        return pass_buffer_as_shm(amshd->fd, amshd->band, buf, size);
 }
 
-__noreturn void afs_init(uint32_t cookie, int socket_fd);
+__noreturn void afs_init(int socket_fd);
 __must_check int afs_event(enum afs_events event, struct para_buffer *pb,
        void *data);
 int send_callback_request(afs_callback *f, struct osl_object *query,
                callback_result_handler *result_handler,
                void *private_result_data);
-int send_option_arg_callback_request(struct osl_object *options,
-               int argc,  char * const * const argv, afs_callback *f,
-               callback_result_handler *result_handler,
-               void *private_result_data);
-int send_standard_callback_request(int argc,  char * const * const argv,
-               afs_callback *f, callback_result_handler *result_handler,
-               void *private_result_data);
+int send_lls_callback_request(afs_callback *f,
+               const struct lls_command * const cmd,
+               struct lls_parse_result *lpr, void *private_result_data);
 int string_compare(const struct osl_object *obj1, const struct osl_object *obj2);
 int for_each_matching_row(struct pattern_match_data *pmd);
 
@@ -254,13 +255,14 @@ int attribute_check_callback(struct afs_callback_arg *aca);
 void aft_init(struct afs_table *t);
 int aft_get_row_of_path(const char *path, struct osl_row **row);
 int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb);
-int open_and_update_audio_file(struct audio_file_data *afd);
+int open_and_update_audio_file(int *fd);
 int load_afd(int shmid, struct audio_file_data *afd);
 int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi);
 int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi);
 int get_audio_file_path_of_row(const struct osl_row *row, char **path);
 int audio_file_loop(void *private_data, osl_rbtree_loop_func *func);
 int aft_check_callback(struct afs_callback_arg *aca);
+void free_status_items(void);
 
 /* playlist */
 int playlist_open(const char *name);
@@ -283,10 +285,12 @@ int playlist_check_callback(struct afs_callback_arg *aca);
                struct para_buffer *pb, void *data); \
        extern struct osl_table *table_name ## _table;
 
+/** \cond blob_symbols */
 DECLARE_BLOB_SYMBOLS(lyrics, lyr);
 DECLARE_BLOB_SYMBOLS(images, img);
 DECLARE_BLOB_SYMBOLS(moods, mood);
 DECLARE_BLOB_SYMBOLS(playlists, pl);
+/** \endcond blob_symbols */
 
 /** The columns of an abstract blob table. */
 enum blob_table_columns {
diff --git a/aft.c b/aft.c
index 1afc16bc542b3812687af3a58df97b941586d885..c8c98e7ab679b5a5254eed9405afa3cca6cf870e 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file aft.c Audio file table functions. */
 
@@ -11,7 +7,9 @@
 #include <fnmatch.h>
 #include <sys/shm.h>
 #include <osl.h>
+#include <lopsub.h>
 
+#include "server_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "crypt.h"
 #include "sideband.h"
 #include "command.h"
 
-static struct osl_table *audio_file_table;
-static char *status_items;
-static char *parser_friendly_status_items;
-
-/** The different sorting methods of the ls command. */
-enum ls_sorting_method {
-       /** -sp (default) */
-       LS_SORT_BY_PATH,
-       /** -ss */
-       LS_SORT_BY_SCORE,
-       /** -sl */
-       LS_SORT_BY_LAST_PLAYED,
-       /** -sn */
-       LS_SORT_BY_NUM_PLAYED,
-       /** -sf */
-       LS_SORT_BY_FREQUENCY,
-       /** -sc */
-       LS_SORT_BY_CHANNELS,
-       /** -si */
-       LS_SORT_BY_IMAGE_ID,
-       /** -sy */
-       LS_SORT_BY_LYRICS_ID,
-       /** -sb */
-       LS_SORT_BY_BITRATE,
-       /** -sd */
-       LS_SORT_BY_DURATION,
-       /** -sa */
-       LS_SORT_BY_AUDIO_FORMAT,
-       /** -sh */
-       LS_SORT_BY_HASH,
-};
-
-/** The different listing modes of the ls command. */
-enum ls_listing_mode {
-       /** Default listing mode. */
-       LS_MODE_SHORT,
-       /** -l or -ll */
-       LS_MODE_LONG,
-       /** -lv */
-       LS_MODE_VERBOSE,
-       /** -lm */
-       LS_MODE_MBOX,
-       /** -lc */
-       LS_MODE_CHUNKS,
-       /** -lp */
-       LS_MODE_PARSER,
-};
-
 /* Data about one audio file. Needed for ls and stat output. */
 struct ls_data {
        /* Usual audio format handler information. */
@@ -86,16 +36,42 @@ struct ls_data {
        unsigned char *hash;
 };
 
-/** The flags accepted by the ls command. */
-enum ls_flags {
-       /** -p */
-       LS_FLAG_FULL_PATH = 1,
-       /** -a */
-       LS_FLAG_ADMISSIBLE_ONLY = 2,
-       /** -r */
-       LS_FLAG_REVERSE = 4,
-       /** -d */
-       LS_FLAG_UNIXDATE = 8,
+/*
+ * The internal state of the audio file table is described by the following
+ * variables which are private to aft.c.
+ */
+static struct osl_table *audio_file_table; /* NULL if table not open */
+static struct osl_row *current_aft_row; /* NULL if no audio file open */
+static unsigned char current_hash[HASH_SIZE]; /* only used on sighup */
+
+static char *status_items;
+static char *parser_friendly_status_items;
+static struct ls_data status_item_ls_data;
+
+/** The different sorting methods of the ls command. */
+enum ls_sorting_method {
+       LS_SORT_BY_PATH, /**< -s=p (default) */
+       LS_SORT_BY_SCORE, /**< -s=s */
+       LS_SORT_BY_LAST_PLAYED, /**< -s=l */
+       LS_SORT_BY_NUM_PLAYED, /**< -s=n */
+       LS_SORT_BY_FREQUENCY, /**< -s=f */
+       LS_SORT_BY_CHANNELS, /**< -s=c */
+       LS_SORT_BY_IMAGE_ID, /**< -s=i */
+       LS_SORT_BY_LYRICS_ID, /**< -s=y */
+       LS_SORT_BY_BITRATE, /**< -s=b */
+       LS_SORT_BY_DURATION, /**< -s=d */
+       LS_SORT_BY_AUDIO_FORMAT, /**< -s=a */
+       LS_SORT_BY_HASH, /**< -s=h */
+};
+
+/** The different listing modes of the ls command. */
+enum ls_listing_mode {
+       LS_MODE_SHORT, /**< Default listing mode. */
+       LS_MODE_LONG, /**< -l or -l=l */
+       LS_MODE_VERBOSE, /** -l=v */
+       LS_MODE_MBOX, /** -l=m */
+       LS_MODE_CHUNKS, /** -l=c */
+       LS_MODE_PARSER, /** -l=p */
 };
 
 /**
@@ -128,16 +104,11 @@ struct ls_widths {
 
 /** Data passed from the ls command handler to its callback function. */
 struct ls_options {
-       /** The given command line flags. */
-       unsigned flags;
-       /** The sorting method given at the command line. */
+       struct lls_parse_result *lpr;
+       /* Derived from lpr */
        enum ls_sorting_method sorting;
-       /** The given listing mode (short, long, verbose, mbox). */
+       /* Derived from lpr */
        enum ls_listing_mode mode;
-       /** The arguments passed to the ls command. */
-       char **patterns;
-       /** Number of non-option arguments. */
-       int num_patterns;
        /** Used for long listing mode to align the output fields. */
        struct ls_widths widths;
        /** Size of the \a data array. */
@@ -153,7 +124,7 @@ struct ls_options {
 /**
  * Describes the layout of the mmapped-afs info struct.
  *
- * \sa struct afs_info.
+ * \sa struct \ref afs_info.
  */
 enum afsi_offsets {
        /** Where .last_played is stored. */
@@ -182,7 +153,7 @@ enum afsi_offsets {
  * \param afsi Pointer to the audio file info to be converted.
  * \param obj Result pointer.
  *
- * \sa load_afsi().
+ * \sa \ref load_afsi().
  */
 static void save_afsi(struct afs_info *afsi, struct osl_object *obj)
 {
@@ -207,7 +178,7 @@ static void save_afsi(struct afs_info *afsi, struct osl_object *obj)
  *
  * \return Standard.
  *
- * \sa save_afsi().
+ * \sa \ref save_afsi().
  */
 static int load_afsi(struct afs_info *afsi, struct osl_object *obj)
 {
@@ -335,8 +306,8 @@ enum afhi_offsets {
        CHUNKS_TOTAL_OFFSET = 20,
        /** The length of the audio file header (4 bytes). */
        HEADER_LEN_OFFSET = 24,
-       /** Was: The start of the audio file header (4 bytes). */
-       AFHI_UNUSED2_OFFSET = 28,
+       /** Size of the largest chunk in bytes. (4 bytes). */
+       AFHI_MAX_CHUNK_SIZE_OFFSET = 28,
        /** The seconds part of the chunk time (4 bytes). */
        CHUNK_TV_TV_SEC_OFFSET = 32,
        /** The microseconds part of the chunk time (4 bytes). */
@@ -376,11 +347,14 @@ static void save_afhi(struct afh_info *afhi, char *buf)
        write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels);
        write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total);
        write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len);
-       write_u32(buf + AFHI_UNUSED2_OFFSET, 0);
+       write_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET, afhi->max_chunk_size);
        write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec);
        write_u32(buf + CHUNK_TV_TV_USEC_OFFSET, afhi->chunk_tv.tv_usec);
        p = buf + AFHI_INFO_STRING_OFFSET;
-       /* The sprintf's below are OK as our caller made sure that buf is large enough */
+       /*
+        * The below sprintf(3) calls are OK because our caller already made
+        * sure that buf is large enough.
+        */
        p += sprintf(p, "%s", afhi->techinfo) + 1;
        p += sprintf(p, "%s", afhi->tags.artist) + 1;
        p += sprintf(p, "%s", afhi->tags.title) + 1;
@@ -389,6 +363,7 @@ static void save_afhi(struct afh_info *afhi, char *buf)
        sprintf(p, "%s", afhi->tags.comment);
 }
 
+/* does not load the chunk table */
 static void load_afhi(const char *buf, struct afh_info *afhi)
 {
        afhi->seconds_total = read_u32(buf + AFHI_SECONDS_TOTAL_OFFSET);
@@ -398,6 +373,7 @@ static void load_afhi(const char *buf, struct afh_info *afhi)
        afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET);
        afhi->chunks_total = read_u32(buf + CHUNKS_TOTAL_OFFSET);
        afhi->header_len = read_u32(buf + HEADER_LEN_OFFSET);
+       afhi->max_chunk_size = read_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET);
        afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET);
        afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC_OFFSET);
        afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET;
@@ -408,42 +384,37 @@ static void load_afhi(const char *buf, struct afh_info *afhi)
        afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1;
 }
 
+/* Only used for saving the chunk table, but not for loading. */
 static unsigned sizeof_chunk_table(struct afh_info *afhi)
 {
-       if (!afhi)
+       if (!afhi || !afhi->chunk_table)
                return 0;
        return 4 * (afhi->chunks_total + 1);
 }
 
-static uint32_t save_chunk_table(struct afh_info *afhi, char *buf)
+static void save_chunk_table(struct afh_info *afhi, char *buf)
 {
-       int i;
-       uint32_t max = 0, old = 0;
+       uint32_t n;
 
-       for (i = 0; i <= afhi->chunks_total; i++) {
-               uint32_t val = afhi->chunk_table[i];
-               write_u32(buf + 4 * i, val);
-               /*
-                * If the first chunk is the header, do not consider it for the
-                * calculation of the largest chunk size.
-                */
-               if (i == 0 || (i == 1 && afhi->header_len > 0)) {
-                       old = val;
-                       continue;
-               }
-               max = PARA_MAX(max, val - old);
-               old = val;
-       }
-       return max;
+       if (!afhi->chunk_table || afhi->chunks_total == 0)
+               return;
+       for (n = 0; n <= afhi->chunks_total; n++)
+               write_u32(buf + 4 * n, afhi->chunk_table[n]);
 }
 
-static void load_chunk_table(struct afh_info *afhi, char *buf)
+static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct)
 {
        int i;
+       size_t sz;
 
-       afhi->chunk_table = para_malloc(sizeof_chunk_table(afhi));
-       for (i = 0; i <= afhi->chunks_total; i++)
-               afhi->chunk_table[i] = read_u32(buf + 4 * i);
+       if (!ct->data || ct->size < 4 * (afhi->chunks_total + 1)) {
+               afhi->chunk_table = NULL;
+               return;
+       }
+       sz  = PARA_MIN(((size_t)afhi->chunks_total + 1) * 4, ct->size) + 1;
+       afhi->chunk_table = para_malloc(sz);
+       for (i = 0; i <= afhi->chunks_total && i * 4 + 3 < ct->size; i++)
+               afhi->chunk_table[i] = read_u32(ct->data + 4 * i);
 }
 
 /**
@@ -547,7 +518,7 @@ static int get_afsi_of_path(const char *path, struct afs_info *afsi)
  * \param row Pointer to a row in the audio file table.
  * \param path Result pointer.
  *
- * The result is a pointer to mmapped data. The caller must not attempt
+ * The result is a pointer to memory-mapped data. The caller must not attempt
  * to free it.
  *
  * \return Standard.
@@ -557,10 +528,12 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path)
        struct osl_object path_obj;
        int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_PATH,
                &path_obj));
+
        if (ret < 0)
-               return ret;
-       *path = path_obj.data;
-       return 1;
+               *path = NULL;
+       else
+               *path = path_obj.data;
+       return ret;
 }
 
 /**
@@ -571,7 +544,7 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path)
  *
  * \return The return value of the underlying call to osl_get_object().
  *
- * \sa get_hash_of_row().
+ * \sa \ref get_hash_of_row().
  */
 static int get_hash_object_of_aft_row(const struct osl_row *row,
                struct osl_object *obj)
@@ -609,13 +582,17 @@ static int get_hash_of_row(const struct osl_row *row, unsigned char **hash)
  *
  * \return The return value of the underlying call to osl_get_object().
  *
- * \sa get_chunk_table_of_row().
+ * After the call the members of the afhi structure point to mapped memory
+ * which is owned by the osl table, Hence the caller must not attempt to free
+ * this memory by calling \ref clear_afhi().
  */
 int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi)
 {
        struct osl_object obj;
-       int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI,
-               &obj));
+       int ret;
+
+       assert(row);
+       ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI, &obj));
        if (ret < 0)
                return ret;
        load_afhi(obj.data, afhi);
@@ -638,7 +615,13 @@ static int save_afd(struct audio_file_data *afd)
                goto err;
        buf = shm_afd;
        buf += sizeof(*afd);
-       afd->max_chunk_size = save_chunk_table(&afd->afhi, buf);
+       save_chunk_table(&afd->afhi, buf);
+       if (afd->afhi.max_chunk_size == 0) { /* v0.5.x on-disk afhi */
+               set_max_chunk_size(&afd->afhi);
+               PARA_NOTICE_LOG("max chunk size unset, re-add required\n");
+       } else
+               PARA_INFO_LOG("using max chunk size from afhi\n");
+       afd->max_chunk_size = afd->afhi.max_chunk_size;
        *(struct audio_file_data *)shm_afd = *afd;
        shm_detach(shm_afd);
        return shmid;
@@ -663,14 +646,22 @@ int load_afd(int shmid, struct audio_file_data *afd)
 {
        void *shm_afd;
        int ret;
+       struct osl_object obj;
 
        ret = shm_attach(shmid, ATTACH_RO, &shm_afd);
        if (ret < 0)
                return ret;
+       ret = shm_size(shmid, &obj.size);
+       if (ret < 0)
+               goto detach;
        *afd = *(struct audio_file_data *)shm_afd;
-       load_chunk_table(&afd->afhi, shm_afd + sizeof(*afd));
+       obj.data = shm_afd + sizeof(*afd);
+       obj.size -= sizeof(*afd);
+       load_chunk_table(&afd->afhi, &obj);
+       ret = 1;
+detach:
        shm_detach(shm_afd);
-       return 1;
+       return ret;
 }
 
 static int get_local_time(uint64_t *seconds, char *buf, size_t size,
@@ -699,7 +690,7 @@ static int get_local_time(uint64_t *seconds, char *buf, size_t size,
        /*
         * If the given time is more than six month away from the current time,
         * we print only the year. The additional space character in the format
-        * string below makes the formated date align nicely with dates that
+        * string below makes the formatted date align nicely with dates that
         * contain the time (those written by the above strftime() statement).
         */
        if (!strftime(buf, size, "%b %e  %Y", tm))
@@ -730,7 +721,8 @@ __a_const static short unsigned get_duration_width(int seconds)
        return width + 6;
 }
 
-static void get_duration_buf(int seconds, char *buf, struct ls_options *opts)
+static void get_duration_buf(int seconds, char *buf, size_t bufsize,
+               struct ls_options *opts)
 {
        unsigned hours = seconds / 3600, mins = (seconds % 3600) / 60;
        short unsigned max_width;
@@ -738,10 +730,12 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts)
        if (!hours) { /* m:ss or mm:ss */
                max_width = opts->mode == LS_MODE_LONG?
                        opts->widths.duration_width : 4;
+               assert(max_width < bufsize - 1);
                sprintf(buf, "%*u:%02d", max_width - 3, mins, seconds % 60);
        } else { /* more than one hour => h:mm:ss, hh:mm:ss, hhh:mm:ss, ... */
                max_width = opts->mode == LS_MODE_LONG?
                        opts->widths.duration_width : 7;
+               assert(max_width < bufsize - 1);
                sprintf(buf, "%*u:%02u:%02d", max_width - 6, hours, mins,
                        seconds % 60);
        }
@@ -753,11 +747,11 @@ static int write_attribute_items(struct para_buffer *b,
        char *att_text;
        int ret;
 
-       WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_BITMAP, "%s\n", att_bitmap);
+       WRITE_STATUS_ITEM(b, SI_attributes_bitmap, "%s\n", att_bitmap);
        ret = get_attribute_text(&afsi->attributes, " ", &att_text);
        if (ret < 0)
                return ret;
-       WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_TXT, "%s\n", att_text);
+       WRITE_STATUS_ITEM(b, SI_attributes_txt, "%s\n", att_text);
        free(att_text);
        return ret;
 }
@@ -766,9 +760,9 @@ static void write_lyrics_items(struct para_buffer *b, struct afs_info *afsi)
 {
        char *lyrics_name;
 
-       WRITE_STATUS_ITEM(b, SI_LYRICS_ID, "%u\n", afsi->lyrics_id);
+       WRITE_STATUS_ITEM(b, SI_lyrics_id, "%u\n", afsi->lyrics_id);
        lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name);
-       WRITE_STATUS_ITEM(b, SI_LYRICS_NAME, "%s\n", lyrics_name?
+       WRITE_STATUS_ITEM(b, SI_lyrics_name, "%s\n", lyrics_name?
                lyrics_name : "(none)");
 }
 
@@ -776,26 +770,26 @@ static void write_image_items(struct para_buffer *b, struct afs_info *afsi)
 {
        char *image_name;
 
-       WRITE_STATUS_ITEM(b, SI_IMAGE_ID, "%u\n", afsi->image_id);
+       WRITE_STATUS_ITEM(b, SI_image_id, "%u\n", afsi->image_id);
        img_get_name_by_id(afsi->image_id, &image_name);
-       WRITE_STATUS_ITEM(b, SI_IMAGE_NAME, "%s\n", image_name?
+       WRITE_STATUS_ITEM(b, SI_image_name, "%s\n", image_name?
                image_name : "(none)");
 }
 
 static void write_filename_items(struct para_buffer *b, const char *path,
-               unsigned flags)
+               bool basename)
 {
        char *val;
 
-       if (!(flags & LS_FLAG_FULL_PATH)) {
-               WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path);
+       if (basename) {
+               WRITE_STATUS_ITEM(b, SI_basename, "%s\n", path);
                return;
        }
-       WRITE_STATUS_ITEM(b, SI_PATH, "%s\n", path);
+       WRITE_STATUS_ITEM(b, SI_path, "%s\n", path);
        val = para_basename(path);
-       WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", val? val : "");
+       WRITE_STATUS_ITEM(b, SI_basename, "%s\n", val? val : "");
        val = para_dirname(path);
-       WRITE_STATUS_ITEM(b, SI_DIRECTORY, "%s\n", val? val : "");
+       WRITE_STATUS_ITEM(b, SI_directory, "%s\n", val? val : "");
        free(val);
 }
 
@@ -820,7 +814,11 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b)
                (long unsigned) d->afhi.chunk_tv.tv_usec
        );
        buf = chunk_table_obj.data;
-       for (i = 0; i <= d->afhi.chunks_total; i++)
+       for (
+               i = 0;
+               i <= d->afhi.chunks_total && 4 * i + 3 < chunk_table_obj.size;
+               i++
+       )
                para_printf(b, "%u ", (unsigned) read_u32(buf + 4 * i));
        para_printf(b, "\n");
        ret = 1;
@@ -828,17 +826,12 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b)
        return ret;
 }
 
-static void write_score(struct para_buffer *b, struct ls_data *d,
-               struct ls_options *opts)
-{
-       if (!(opts->flags & LS_FLAG_ADMISSIBLE_ONLY)) /* no score*/
-               return;
-       WRITE_STATUS_ITEM(b, SI_SCORE, "%li\n", d->score);
-}
-
 static int print_list_item(struct ls_data *d, struct ls_options *opts,
        struct para_buffer *b, time_t current_time)
 {
+       const struct lls_opt_result *r_a = SERVER_CMD_OPT_RESULT(LS, ADMISSIBLE, opts->lpr);
+       const struct lls_opt_result *r_b = SERVER_CMD_OPT_RESULT(LS, BASENAME, opts->lpr);
+       const struct lls_opt_result *r_d = SERVER_CMD_OPT_RESULT(LS, UNIX_DATE, opts->lpr);
        int ret;
        char att_buf[65];
        char last_played_time[30];
@@ -857,7 +850,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                goto out;
        }
        get_attribute_bitmap(&afsi->attributes, att_buf);
-       if (opts->flags & LS_FLAG_UNIXDATE)
+       if (lls_opt_given(r_d))
                sprintf(last_played_time, "%llu",
                        (long long unsigned)afsi->last_played);
        else {
@@ -866,13 +859,13 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                if (ret < 0)
                        goto out;
        }
-       get_duration_buf(afhi->seconds_total, duration_buf, opts);
+       get_duration_buf(afhi->seconds_total, duration_buf,
+               sizeof(duration_buf), opts);
        if (opts->mode == LS_MODE_LONG) {
                struct ls_widths *w = &opts->widths;
-               if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+               if (lls_opt_given(r_a))
                        para_printf(b, "%*li ", opts->widths.score_width,
                                d->score);
-               }
                para_printf(b,
                        "%s "   /* attributes */
                        "%*u "  /* amp */
@@ -912,35 +905,38 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                        last_played_time,
                        bn? bn : "?");
        }
-       write_filename_items(b, d->path, opts->flags);
-       write_score(b, d, opts);
+       write_filename_items(b, d->path, lls_opt_given(r_b));
+       if (lls_opt_given(r_a))
+               WRITE_STATUS_ITEM(b, SI_score, "%li\n", d->score);
        ret = write_attribute_items(b, att_buf, afsi);
        if (ret < 0)
                goto out;
        write_image_items(b, afsi);
        write_lyrics_items(b, afsi);
        hash_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",
+       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",
                audio_format_name(afsi->audio_format_id));
-       WRITE_STATUS_ITEM(b, SI_FREQUENCY, "%dHz\n", afhi->frequency);
-       WRITE_STATUS_ITEM(b, SI_CHANNELS, "%d\n", afhi->channels);
-       WRITE_STATUS_ITEM(b, SI_DURATION, "%s\n", duration_buf);
-       WRITE_STATUS_ITEM(b, SI_SECONDS_TOTAL, "%" PRIu32 "\n",
+       WRITE_STATUS_ITEM(b, SI_frequency, "%dHz\n", afhi->frequency);
+       WRITE_STATUS_ITEM(b, SI_channels, "%d\n", afhi->channels);
+       WRITE_STATUS_ITEM(b, SI_duration, "%s\n", duration_buf);
+       WRITE_STATUS_ITEM(b, SI_seconds_total, "%" PRIu32 "\n",
                afhi->seconds_total);
-       WRITE_STATUS_ITEM(b, SI_LAST_PLAYED, "%s\n", last_played_time);
-       WRITE_STATUS_ITEM(b, SI_NUM_PLAYED, "%u\n", afsi->num_played);
-       WRITE_STATUS_ITEM(b, SI_AMPLIFICATION, "%u\n", afsi->amp);
-       WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n", tv2ms(&afhi->chunk_tv));
-       WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%" PRIu32 "\n",
+       WRITE_STATUS_ITEM(b, SI_last_played, "%s\n", last_played_time);
+       WRITE_STATUS_ITEM(b, SI_num_played, "%u\n", afsi->num_played);
+       WRITE_STATUS_ITEM(b, SI_amplification, "%u\n", afsi->amp);
+       WRITE_STATUS_ITEM(b, SI_chunk_time, "%lu\n", tv2ms(&afhi->chunk_tv));
+       WRITE_STATUS_ITEM(b, SI_num_chunks, "%" PRIu32 "\n",
                afhi->chunks_total);
-       WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo);
-       WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist);
-       WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title);
-       WRITE_STATUS_ITEM(b, SI_YEAR, "%s\n", afhi->tags.year);
-       WRITE_STATUS_ITEM(b, SI_ALBUM, "%s\n", afhi->tags.album);
-       WRITE_STATUS_ITEM(b, SI_COMMENT, "%s\n", afhi->tags.comment);
+       WRITE_STATUS_ITEM(b, SI_max_chunk_size, "%" PRIu32 "\n",
+               afhi->max_chunk_size);
+       WRITE_STATUS_ITEM(b, SI_techinfo, "%s\n", afhi->techinfo);
+       WRITE_STATUS_ITEM(b, SI_artist, "%s\n", afhi->tags.artist);
+       WRITE_STATUS_ITEM(b, SI_title, "%s\n", afhi->tags.title);
+       WRITE_STATUS_ITEM(b, SI_year, "%s\n", afhi->tags.year);
+       WRITE_STATUS_ITEM(b, SI_album, "%s\n", afhi->tags.album);
+       WRITE_STATUS_ITEM(b, SI_comment, "%s\n", afhi->tags.comment);
        if (opts->mode == LS_MODE_MBOX) {
                struct osl_object lyrics_def;
                lyr_get_def_by_id(afsi->lyrics_id, &lyrics_def);
@@ -954,9 +950,6 @@ out:
        return ret;
 }
 
-static struct ls_data status_item_ls_data;
-static struct osl_row *current_aft_row;
-
 static void make_inode_status_items(struct para_buffer *pb)
 {
        struct stat statbuf = {.st_size = 0};
@@ -974,46 +967,71 @@ static void make_inode_status_items(struct para_buffer *pb)
        ret = strftime(mtime_str, 29, "%b %d %Y", &mtime_tm);
        assert(ret > 0); /* number of bytes placed in mtime_str */
 out:
-       WRITE_STATUS_ITEM(pb, SI_MTIME, "%s\n", mtime_str);
-       WRITE_STATUS_ITEM(pb, SI_FILE_SIZE, "%ld\n", statbuf.st_size / 1024);
+       WRITE_STATUS_ITEM(pb, SI_mtime, "%s\n", mtime_str);
+       WRITE_STATUS_ITEM(pb, SI_file_size, "%ld\n", statbuf.st_size / 1024);
+}
+
+/**
+ * Deallocate and invalidate the status item strings.
+ *
+ * This needs to be a public function so that afs.c can call it on shutdown.
+ */
+void free_status_items(void)
+{
+       freep(&status_items);
+       freep(&parser_friendly_status_items);
 }
 
-static int make_status_items(void)
+static void make_status_items(void)
 {
-       struct ls_options opts = {
-               .flags = LS_FLAG_FULL_PATH | LS_FLAG_ADMISSIBLE_ONLY,
-               .mode = LS_MODE_VERBOSE,
-       };
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS);
+       char *argv[] = {"ls", "--admissible", "--listing-mode=verbose"};
+       struct ls_options opts = {.mode = LS_MODE_VERBOSE};
        struct para_buffer pb = {.max_size = shm_get_shmmax() - 1};
        time_t current_time;
        int ret;
 
+       free_status_items();
+       if (!status_item_ls_data.path) /* no audio file open */
+               return;
+       ret = lls_parse(ARRAY_SIZE(argv), argv, cmd, &opts.lpr, NULL);
+       assert(ret >= 0);
        time(&current_time);
        ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time);
        if (ret < 0)
-               return ret;
+               goto out;
        make_inode_status_items(&pb);
-       free(status_items);
        status_items = pb.buf;
+
        memset(&pb, 0, sizeof(pb));
        pb.max_size = shm_get_shmmax() - 1;
        pb.flags = PBF_SIZE_PREFIX;
        ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time);
-       if (ret < 0) {
-               free(status_items);
-               status_items = NULL;
-               return ret;
-       }
+       if (ret < 0)
+               goto out;
        make_inode_status_items(&pb);
-       free(parser_friendly_status_items);
        parser_friendly_status_items = pb.buf;
-       return 1;
+       ret = 1;
+out:
+       if (ret < 0) {
+               PARA_WARNING_LOG("could not create status items: %s\n",
+                       para_strerror(-ret));
+               free_status_items();
+       }
+       lls_free_parse_result(opts.lpr, cmd);
 }
 
 /**
  * Open the audio file with highest score and set up an afd structure.
  *
- * \param afd Result pointer.
+ * This determines and opens the next audio file, verifies that it did not
+ * change by comparing the recomputed the hash value of the file contents
+ * against the value stored in the audio file table. If all goes well, it
+ * creates a shared memory area containing the serialized version of the afd
+ * structure, including the chunk table, if any. The caller can then send the
+ * ID of this area and the open fd to the server process.
+ *
+ * \param fd Result pointer for the file descriptor of the audio file.
  *
  * On success, the numplayed field of the audio file selector info is increased
  * and the lastplayed time is set to the current time. Finally, the score of
@@ -1021,7 +1039,7 @@ static int make_status_items(void)
  *
  * \return Positive shmid on success, negative on errors.
  */
-int open_and_update_audio_file(struct audio_file_data *afd)
+int open_and_update_audio_file(int *fd)
 {
        unsigned char file_hash[HASH_SIZE];
        struct osl_object afsi_obj;
@@ -1030,33 +1048,57 @@ int open_and_update_audio_file(struct audio_file_data *afd)
        struct afsi_change_event_data aced;
        struct osl_object map, chunk_table_obj;
        struct ls_data *d = &status_item_ls_data;
+       unsigned char *tmp_hash;
+       struct audio_file_data afd;
 again:
        ret = score_get_best(&current_aft_row, &d->score);
        if (ret < 0)
                return ret;
-       ret = get_hash_of_row(current_aft_row, &d->hash);
+       /*
+        * get_hash_of_row() and get_audio_file_path_of_row() initialize
+        * their pointer argument to point to memory-mapped files. These pointers
+        * become stale after a new audio file has been added or after the
+        * server process received SIGHUP. For in both cases libosl unmaps and
+        * remaps the underlying database files, and this remapping may well
+        * change the starting address of the mapping. To avoid stale pointer
+        * references we create copies on the heap.
+        */
+       ret = get_hash_of_row(current_aft_row, &tmp_hash);
        if (ret < 0)
                return ret;
+       if (!d->hash)
+               d->hash = para_malloc(HASH_SIZE);
+       memcpy(d->hash, tmp_hash, HASH_SIZE);
+       free(d->path);
        ret = get_audio_file_path_of_row(current_aft_row, &d->path);
        if (ret < 0)
                return ret;
        PARA_NOTICE_LOG("%s\n", d->path);
+       d->path = para_strdup(d->path);
+
        ret = get_afsi_object_of_row(current_aft_row, &afsi_obj);
        if (ret < 0)
                return ret;
        ret = load_afsi(&d->afsi, &afsi_obj);
        if (ret < 0)
                return ret;
-       ret = get_afhi_of_row(current_aft_row, &afd->afhi);
+       ret = get_afhi_of_row(current_aft_row, &afd.afhi);
        if (ret < 0)
                return ret;
-       d->afhi = afd->afhi;
-       d->afhi.chunk_table = afd->afhi.chunk_table = NULL;
+       d->afhi = afd.afhi;
+       d->afhi.chunk_table = afd.afhi.chunk_table = NULL;
        ret = osl(osl_open_disk_object(audio_file_table, current_aft_row,
                AFTCOL_CHUNKS, &chunk_table_obj));
-       if (ret < 0)
-               return ret;
-       ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd);
+       if (ret < 0) {
+               if (!afh_supports_dynamic_chunks(d->afsi.audio_format_id))
+                       return ret;
+               PARA_INFO_LOG("no chunk table for %s\n", d->path);
+               chunk_table_obj.data = NULL;
+               chunk_table_obj.size = 0;
+       } else {
+               PARA_INFO_LOG("chunk table: %zu bytes\n", chunk_table_obj.size);
+       }
+       ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, fd);
        if (ret < 0)
                goto out;
        hash_function(map.data, map.size, file_hash);
@@ -1071,8 +1113,8 @@ again:
        new_afsi.last_played = time(NULL);
        save_afsi(&new_afsi, &afsi_obj); /* in-place update */
 
-       afd->audio_format_id = d->afsi.audio_format_id;
-       load_chunk_table(&afd->afhi, chunk_table_obj.data);
+       afd.audio_format_id = d->afsi.audio_format_id;
+       load_chunk_table(&afd.afhi, &chunk_table_obj);
        aced.aft_row = current_aft_row;
        aced.old_afsi = &d->afsi;
        /*
@@ -1082,10 +1124,11 @@ again:
        ret = afs_event(AFSI_CHANGE, NULL, &aced);
        if (ret < 0)
                goto out;
-       ret = save_afd(afd);
+       ret = save_afd(&afd);
 out:
-       free(afd->afhi.chunk_table);
-       osl_close_disk_object(&chunk_table_obj);
+       free(afd.afhi.chunk_table);
+       if (chunk_table_obj.data)
+               osl_close_disk_object(&chunk_table_obj);
        if (ret < 0) {
                PARA_ERROR_LOG("%s: %s\n", d->path, para_strerror(-ret));
                ret = score_delete(current_aft_row);
@@ -1095,6 +1138,12 @@ out:
        return ret;
 }
 
+static int ls_hash_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return memcmp(d1->hash, d2->hash, HASH_SIZE);
+}
+
 static int ls_audio_format_compare(const void *a, const void *b)
 {
        struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
@@ -1161,8 +1210,16 @@ static int ls_path_compare(const void *a, const void *b)
        return strcmp(d1->path, d2->path);
 }
 
+static inline bool admissible_only(struct ls_options *opts)
+{
+       return SERVER_CMD_OPT_GIVEN(LS, ADMISSIBLE, opts->lpr)
+               || opts->sorting == LS_SORT_BY_SCORE;
+}
+
 static int sort_matching_paths(struct ls_options *options)
 {
+       const struct lls_opt_result *r_b = SERVER_CMD_OPT_RESULT(LS, BASENAME,
+               options->lpr);
        size_t nmemb = options->num_matching_paths;
        size_t size = sizeof(*options->data_ptr);
        int (*compar)(const void *, const void *);
@@ -1173,13 +1230,13 @@ static int sort_matching_paths(struct ls_options *options)
                options->data_ptr[i] = options->data + i;
 
        /* In these cases the array is already sorted */
-       if (options->sorting == LS_SORT_BY_PATH
-               && !(options->flags & LS_FLAG_ADMISSIBLE_ONLY)
-               && (options->flags & LS_FLAG_FULL_PATH))
-               return 1;
-       if (options->sorting == LS_SORT_BY_SCORE &&
-                       options->flags & LS_FLAG_ADMISSIBLE_ONLY)
-               return 1;
+       if (admissible_only(options)) {
+               if (options->sorting == LS_SORT_BY_SCORE)
+                       return 1;
+       } else {
+               if (options->sorting == LS_SORT_BY_PATH && !lls_opt_given(r_b))
+                       return 1;
+       }
 
        switch (options->sorting) {
        case LS_SORT_BY_PATH:
@@ -1204,6 +1261,8 @@ static int sort_matching_paths(struct ls_options *options)
                compar = ls_duration_compare; break;
        case LS_SORT_BY_AUDIO_FORMAT:
                compar = ls_audio_format_compare; break;
+       case LS_SORT_BY_HASH:
+               compar = ls_hash_compare; break;
        default:
                return -E_BAD_SORT;
        }
@@ -1217,15 +1276,16 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
 {
        int ret, i;
        struct ls_options *options = ls_opts;
+       bool basename_given = SERVER_CMD_OPT_GIVEN(LS, BASENAME, options->lpr);
        struct ls_data *d;
        struct ls_widths *w;
        unsigned short num_digits;
-       unsigned tmp;
+       unsigned tmp, num_inputs;
        struct osl_row *aft_row;
        long score;
        char *path;
 
-       if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+       if (admissible_only(options)) {
                ret = get_score_and_aft_row(row, &score, &aft_row);
                if (ret < 0)
                        return ret;
@@ -1236,21 +1296,22 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
        ret = get_audio_file_path_of_row(aft_row, &path);
        if (ret < 0)
                return ret;
-       if (!(options->flags & LS_FLAG_FULL_PATH)) {
+       if (basename_given) {
                char *p = strrchr(path, '/');
                if (p)
                        path = p + 1;
        }
-       if (options->num_patterns) {
-               for (i = 0; i < options->num_patterns; i++) {
-                       ret = fnmatch(options->patterns[i], path, 0);
+       num_inputs = lls_num_inputs(options->lpr);
+       if (num_inputs > 0) {
+               for (i = 0; i < num_inputs; i++) {
+                       ret = fnmatch(lls_input(i, options->lpr), path, 0);
                        if (!ret)
                                break;
                        if (ret == FNM_NOMATCH)
                                continue;
                        return -E_FNMATCH;
                }
-               if (i >= options->num_patterns) /* no match */
+               if (i >= num_inputs) /* no match */
                        return 1;
        }
        tmp = options->num_matching_paths++;
@@ -1289,7 +1350,7 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
        w->amp_width = PARA_MAX(w->amp_width, num_digits);
        num_digits = strlen(audio_format_name(d->afsi.audio_format_id));
        w->audio_format_width = PARA_MAX(w->audio_format_width, num_digits);
-       if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+       if (admissible_only(options)) {
                GET_NUM_DIGITS(score, &num_digits);
                num_digits++; /* add one for the sign (space or "-") */
                w->score_width = PARA_MAX(w->score_width, num_digits);
@@ -1302,21 +1363,19 @@ err:
 
 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;
-       char *p, *pattern_start = (char *)aca->query.data + sizeof(*opts);
        int i = 0, ret;
        time_t current_time;
+       const struct lls_opt_result *r_r;
+
+       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);
 
        aca->pbout.flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0;
-       if (opts->num_patterns) {
-               opts->patterns = para_malloc(opts->num_patterns * sizeof(char *));
-               for (i = 0, p = pattern_start; i < opts->num_patterns; i++) {
-                       opts->patterns[i] = p;
-                       p += strlen(p) + 1;
-               }
-       } else
-               opts->patterns = NULL;
-       if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY)
+       if (admissible_only(opts))
                ret = admissible_file_loop(opts, prepare_ls_row);
        else
                ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts,
@@ -1324,14 +1383,14 @@ static int com_ls_callback(struct afs_callback_arg *aca)
        if (ret < 0)
                goto out;
        if (opts->num_matching_paths == 0) {
-               ret = opts->num_patterns > 0? -E_NO_MATCH : 0;
+               ret = lls_num_inputs(opts->lpr) > 0? -E_NO_MATCH : 0;
                goto out;
        }
        ret = sort_matching_paths(opts);
        if (ret < 0)
                goto out;
        time(&current_time);
-       if (opts->flags & LS_FLAG_REVERSE)
+       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);
@@ -1346,143 +1405,91 @@ static int com_ls_callback(struct afs_callback_arg *aca)
                                goto out;
                }
 out:
+       lls_free_parse_result(opts->lpr, cmd);
        free(opts->data);
        free(opts->data_ptr);
-       free(opts->patterns);
        return ret;
 }
 
-/*
- * TODO: flags -h (sort by hash)
- */
-int com_ls(struct command_context *cc)
+static int com_ls(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       int i;
-       unsigned flags = 0;
-       enum ls_sorting_method sort = LS_SORT_BY_PATH;
-       enum ls_listing_mode mode = LS_MODE_SHORT;
-       struct ls_options opts = {.patterns = NULL};
-       struct osl_object query = {.data = &opts, .size = sizeof(opts)};
-
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               /*
-                * Compatibility: Prior to 0.5.5 it was necessary to specify
-                * the listing mode without the '=' character as in -lv, for
-                * example. Now the variant with '=' is preferred and
-                * documented but we still accept the old way to specify the
-                * listing mode.
-                *
-                * Support for the legacy syntax can be dropped at 0.6.0
-                * or later.
-                */
-               if (!strncmp(arg, "-l", 2)) {
-                       arg += 2;
-                       if (*arg == '=')
-                               arg++;
-                       switch (*arg) {
-                       case 's':
-                               mode = LS_MODE_SHORT;
-                               continue;
-                       case 'l':
-                       case '\0':
-                               mode = LS_MODE_LONG;
-                               continue;
-                       case 'v':
-                               mode = LS_MODE_VERBOSE;
-                               continue;
-                       case 'm':
-                               mode = LS_MODE_MBOX;
-                               continue;
-                       case 'c':
-                               mode = LS_MODE_CHUNKS;
-                               continue;
-                       case 'p':
-                               mode = LS_MODE_PARSER;
-                               continue;
-                       default:
-                               return -E_AFT_SYNTAX;
-                       }
-               }
-               if (!strcmp(arg, "-p") || !strcmp(arg, "-F")) {
-                       flags |= LS_FLAG_FULL_PATH;
-                       continue;
-               }
-               if (!strcmp(arg, "-b")) {
-                       flags &= ~LS_FLAG_FULL_PATH;
-                       continue;
-               }
-               if (!strcmp(arg, "-a")) {
-                       flags |= LS_FLAG_ADMISSIBLE_ONLY;
-                       continue;
-               }
-               if (!strcmp(arg, "-r")) {
-                       flags |= LS_FLAG_REVERSE;
-                       continue;
-               }
-               if (!strcmp(arg, "-d")) {
-                       flags |= LS_FLAG_UNIXDATE;
-                       continue;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS);
+       struct ls_options *opts;
+       struct osl_object query;
+       const struct lls_opt_result *r_l = SERVER_CMD_OPT_RESULT(LS, LISTING_MODE,
+               lpr);
+       const struct lls_opt_result *r_s = SERVER_CMD_OPT_RESULT(LS, SORT, lpr);
+       int ret;
+       char *slpr;
+
+       ret = lls_serialize_parse_result(lpr, cmd, NULL, &query.size);
+       assert(ret >= 0);
+       query.size += sizeof(*opts);
+       query.data = para_malloc(query.size);
+       opts = query.data;
+       memset(opts, 0, sizeof(*opts));
+       slpr = query.data + sizeof(*opts);
+       ret = lls_serialize_parse_result(lpr, cmd, &slpr, NULL);
+       assert(ret >= 0);
+       opts->mode = LS_MODE_SHORT;
+       opts->sorting = LS_SORT_BY_PATH;
+       if (lls_opt_given(r_l)) {
+               const char *val = lls_string_val(0, r_l);
+               if (!strcmp(val, "l") || !strcmp(val, "long"))
+                       opts->mode = LS_MODE_LONG;
+               else if (!strcmp(val, "s") || !strcmp(val, "short"))
+                       opts->mode = LS_MODE_SHORT;
+               else if (!strcmp(val, "v") || !strcmp(val, "verbose"))
+                       opts->mode = LS_MODE_VERBOSE;
+               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;
+               else if (!strcmp(val, "p") || !strcmp(val, "parser-friendly"))
+                       opts->mode = LS_MODE_PARSER;
+               else {
+                       ret = -E_AFT_SYNTAX;
+                       goto out;
                }
-               /* The compatibility remark above applies also to -s. */
-               if (!strncmp(arg, "-s", 2)) {
-                       arg += 2;
-                       if (*arg == '=')
-                               arg++;
-                       switch (*arg) {
-                       case 'p':
-                               sort = LS_SORT_BY_PATH;
-                               continue;
-                       case 's': /* -ss implies -a */
-                               sort = LS_SORT_BY_SCORE;
-                               flags |= LS_FLAG_ADMISSIBLE_ONLY;
-                               continue;
-                       case 'l':
-                               sort = LS_SORT_BY_LAST_PLAYED;
-                               continue;
-                       case 'n':
-                               sort = LS_SORT_BY_NUM_PLAYED;
-                               continue;
-                       case 'f':
-                               sort = LS_SORT_BY_FREQUENCY;
-                               continue;
-                       case 'c':
-                               sort = LS_SORT_BY_CHANNELS;
-                               continue;
-                       case 'i':
-                               sort = LS_SORT_BY_IMAGE_ID;
-                               continue;
-                       case 'y':
-                               sort = LS_SORT_BY_LYRICS_ID;
-                               continue;
-                       case 'b':
-                               sort = LS_SORT_BY_BITRATE;
-                               continue;
-                       case 'd':
-                               sort = LS_SORT_BY_DURATION;
-                               continue;
-                       case 'a':
-                               sort = LS_SORT_BY_AUDIO_FORMAT;
-                               continue;
-                       default:
-                               return -E_AFT_SYNTAX;
-                       }
+       }
+       if (lls_opt_given(r_s)) {
+               const char *val = lls_string_val(0, r_s);
+               if (!strcmp(val, "p") || !strcmp(val, "path"))
+                       opts->sorting = LS_SORT_BY_PATH;
+               else if (!strcmp(val, "s") || !strcmp(val, "score"))
+                       opts->sorting = LS_SORT_BY_SCORE;
+               else if (!strcmp(val, "l") || !strcmp(val, "lastplayed"))
+                       opts->sorting = LS_SORT_BY_LAST_PLAYED;
+               else if (!strcmp(val, "n") || !strcmp(val, "numplayed"))
+                       opts->sorting = LS_SORT_BY_NUM_PLAYED;
+               else if (!strcmp(val, "f") || !strcmp(val, "frquency"))
+                       opts->sorting = LS_SORT_BY_FREQUENCY;
+               else if (!strcmp(val, "c") || !strcmp(val, "channels"))
+                       opts->sorting = LS_SORT_BY_CHANNELS;
+               else if (!strcmp(val, "i") || !strcmp(val, "image-id"))
+                       opts->sorting = LS_SORT_BY_IMAGE_ID;
+               else if (!strcmp(val, "y") || !strcmp(val, "lyrics-id"))
+                       opts->sorting = LS_SORT_BY_LYRICS_ID;
+               else if (!strcmp(val, "b") || !strcmp(val, "bitrate"))
+                       opts->sorting = LS_SORT_BY_BITRATE;
+               else if (!strcmp(val, "d") || !strcmp(val, "duration"))
+                       opts->sorting = LS_SORT_BY_DURATION;
+               else if (!strcmp(val, "a") || !strcmp(val, "audio-format"))
+                       opts->sorting = LS_SORT_BY_AUDIO_FORMAT;
+               else if (!strcmp(val, "h") || !strcmp(val, "hash"))
+                       opts->sorting = LS_SORT_BY_HASH;
+               else {
+                       ret = -E_AFT_SYNTAX;
+                       goto out;
                }
-               return -E_AFT_SYNTAX;
        }
-       opts.flags = flags;
-       opts.sorting = sort;
-       opts.mode = mode;
-       opts.num_patterns = cc->argc - i;
-       return send_option_arg_callback_request(&query, opts.num_patterns,
-               cc->argv + i, com_ls_callback, afs_cb_result_handler, cc);
+       ret = send_callback_request(com_ls_callback, &query,
+               afs_cb_result_handler, cc);
+out:
+       free(query.data);
+       return ret;
 }
+EXPORT_SERVER_CMD_HANDLER(ls);
 
 /**
  * Call the given function for each file in the audio file table.
@@ -1522,8 +1529,8 @@ enum com_add_buffer_offsets {
        CAB_AFHI_OFFSET_POS = 0,
        /** Start of the chunk table (if present). */
        CAB_CHUNKS_OFFSET_POS = 4,
-       /** Flags given to the add command. */
-       CAB_FLAGS_OFFSET = 8,
+       /** Start of the (serialized) lopsub parse result. */
+       CAB_LPR_OFFSET = 8,
        /** Audio format id. */
        CAB_AUDIO_FORMAT_ID_OFFSET = 12,
        /** The hash of the audio file being added. */
@@ -1540,31 +1547,32 @@ enum com_add_buffer_offsets {
  * handler info won't be stored in the buffer.
  */
 static void save_add_callback_buffer(unsigned char *hash, const char *path,
-               struct afh_info *afhi, uint32_t flags,
+               struct afh_info *afhi, const char *slpr, size_t slpr_size,
                uint8_t audio_format_num, struct osl_object *obj)
 {
        size_t path_len = strlen(path) + 1;
        size_t afhi_size = sizeof_afhi_buf(afhi);
        size_t size = CAB_PATH_OFFSET + path_len + afhi_size
-               + sizeof_chunk_table(afhi);
+               + sizeof_chunk_table(afhi) + slpr_size;
        char *buf = para_malloc(size);
        uint32_t pos;
 
+       assert(size <= ~(uint32_t)0);
+       write_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET, audio_format_num);
+       memcpy(buf + CAB_HASH_OFFSET, hash, HASH_SIZE);
+       strcpy(buf + CAB_PATH_OFFSET, path);
        pos = CAB_PATH_OFFSET + path_len;
        write_u32(buf + CAB_AFHI_OFFSET_POS, pos);
        save_afhi(afhi, buf + pos);
        pos += afhi_size;
-
        write_u32(buf + CAB_CHUNKS_OFFSET_POS, pos);
-       if (afhi)
+       if (afhi) {
                save_chunk_table(afhi, buf + pos);
-
-       write_u32(buf + CAB_FLAGS_OFFSET, flags);
-       write_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET, audio_format_num);
-
-       memcpy(buf + CAB_HASH_OFFSET, hash, HASH_SIZE);
-       strcpy(buf + CAB_PATH_OFFSET, path);
-
+               pos += sizeof_chunk_table(afhi);
+       }
+       write_u32(buf + CAB_LPR_OFFSET, pos);
+       memcpy(buf + pos, slpr, slpr_size);
+       assert(pos + slpr_size == size);
        obj->data = buf;
        obj->size = size;
 }
@@ -1610,7 +1618,7 @@ ACTION:   Table modifications to be done by the callback.
 +----+----+---+------+---------------------------------------------------+
 | N  |  N | Y |  Y   | (new file) create new entry (force has no effect)
 +----+----+---+------+---------------------------------------------------+
-|  N |  N | N |  Y   | (new file) create new entry
+|  |  N | N |  Y   | (new file) create new entry
 +----+----+---+------+---------------------------------------------------+
 
 Notes:
@@ -1620,18 +1628,6 @@ Notes:
 
 */
 
-/** Flags passed to the add command. */
-enum com_add_flags {
-       /** Skip paths that exist already. */
-       ADD_FLAG_LAZY = 1,
-       /** Force adding. */
-       ADD_FLAG_FORCE = 2,
-       /** Print what is being done. */
-       ADD_FLAG_VERBOSE = 4,
-       /** Try to add files with unknown suffixes. */
-       ADD_FLAG_ALL = 8,
-};
-
 static int com_add_callback(struct afs_callback_arg *aca)
 {
        char *buf = aca->query.data, *path;
@@ -1642,9 +1638,16 @@ static int com_add_callback(struct afs_callback_arg *aca)
        char asc[2 * HASH_SIZE + 1];
        int ret;
        char afsi_buf[AFSI_SIZE];
-       uint32_t flags = read_u32(buf + CAB_FLAGS_OFFSET);
+       char *slpr = buf + read_u32(buf + CAB_LPR_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);
+       const struct lls_opt_result *r_f, *r_v;
+
+       ret = lls_deserialize_parse_result(slpr, cmd, &aca->lpr);
+       assert(ret >= 0);
+       r_f = SERVER_CMD_OPT_RESULT(ADD, FORCE, aca->lpr);
+       r_v = SERVER_CMD_OPT_RESULT(ADD, VERBOSE, aca->lpr);
 
        hash = (unsigned char *)buf + CAB_HASH_OFFSET;
        hash_to_asc(hash, asc);
@@ -1662,8 +1665,8 @@ static int com_add_callback(struct afs_callback_arg *aca)
        ret = find_path_brother(path, &pb);
        if (ret < 0)
                goto out;
-       if (hs && pb && hs == pb && !(flags & ADD_FLAG_FORCE)) {
-               if (flags & ADD_FLAG_VERBOSE)
+       if (hs && pb && hs == pb && !lls_opt_given(r_f)) {
+               if (lls_opt_given(r_v))
                        para_printf(&aca->pbout, "ignoring duplicate\n");
                ret = 1;
                goto out;
@@ -1671,7 +1674,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
        if (hs && hs != pb) {
                struct osl_object obj;
                if (pb) { /* hs trumps pb, remove pb */
-                       if (flags & ADD_FLAG_VERBOSE)
+                       if (lls_opt_given(r_v))
                                para_printf(&aca->pbout, "removing %s\n", path);
                        ret = afs_event(AUDIO_FILE_REMOVE, &aca->pbout, pb);
                        if (ret < 0)
@@ -1682,7 +1685,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
                        pb = NULL;
                }
                /* file rename, update hs' path */
-               if (flags & ADD_FLAG_VERBOSE) {
+               if (lls_opt_given(r_v)) {
                        ret = osl(osl_get_object(audio_file_table, hs,
                                AFTCOL_PATH, &obj));
                        if (ret < 0)
@@ -1697,7 +1700,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
                ret = afs_event(AUDIO_FILE_RENAME, &aca->pbout, hs);
                if (ret < 0)
                        goto out;
-               if (!(flags & ADD_FLAG_FORCE))
+               if (!lls_opt_given(r_f))
                        goto out;
        }
        /* no hs or force mode, child must have sent afhi */
@@ -1718,35 +1721,34 @@ static int com_add_callback(struct afs_callback_arg *aca)
                if (ret < 0)
                        goto out;
                hash_to_asc(old_hash, old_asc);
-               if (flags & ADD_FLAG_VERBOSE)
+               if (lls_opt_given(r_v))
                        para_printf(&aca->pbout, "file change: %s -> %s\n",
                                old_asc, asc);
-               ret = osl_update_object(audio_file_table, pb, AFTCOL_HASH,
-                       &objs[AFTCOL_HASH]);
+               ret = osl(osl_update_object(audio_file_table, pb, AFTCOL_HASH,
+                       &objs[AFTCOL_HASH]));
                if (ret < 0)
                        goto out;
        }
        if (hs || pb) { /* (hs != NULL and pb != NULL) implies hs == pb */
                struct osl_row *row = pb? pb : hs;
                /* update afhi and chunk_table */
-               if (flags & ADD_FLAG_VERBOSE)
+               if (lls_opt_given(r_v))
                        para_printf(&aca->pbout,
                                "updating afhi and chunk table\n");
                ret = osl(osl_update_object(audio_file_table, row, AFTCOL_AFHI,
                        &objs[AFTCOL_AFHI]));
                if (ret < 0)
                        goto out;
+               /* truncate the file to size zero if there is no chunk table */
                ret = osl(osl_update_object(audio_file_table, row, AFTCOL_CHUNKS,
                        &objs[AFTCOL_CHUNKS]));
                if (ret < 0)
                        goto out;
                ret = afs_event(AFHI_CHANGE, &aca->pbout, row);
-               if (ret < 0)
-                       goto out;
                goto out;
        }
        /* new entry, use default afsi */
-       if (flags & ADD_FLAG_VERBOSE)
+       if (lls_opt_given(r_v))
                para_printf(&aca->pbout, "new file\n");
        default_afsi.last_played = time(NULL) - 365 * 24 * 60 * 60;
        default_afsi.audio_format_id = read_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET);
@@ -1761,15 +1763,20 @@ static int com_add_callback(struct afs_callback_arg *aca)
 out:
        if (ret < 0)
                para_printf(&aca->pbout, "could not add %s\n", path);
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-/** Used by com_add(). */
+/* Used by com_add(). */
 struct private_add_data {
-       /** The pointer passed to the original command handler. */
+       /* The pointer passed to the original command handler. */
        struct command_context *cc;
-       /** The given add flags. */
-       uint32_t flags;
+       /* Contains the flags given at the command line. */
+       struct lls_parse_result *lpr;
+       /* Serialized lopsub parse result. */
+       char *slpr;
+       /* Number of bytes. */
+       size_t slpr_size;
 };
 
 static int path_brother_callback(struct afs_callback_arg *aca)
@@ -1814,9 +1821,13 @@ static int add_one_audio_file(const char *path, void *private_data)
        struct osl_row *pb = NULL, *hs = NULL; /* path brother/hash sister */
        struct osl_object map, obj = {.data = NULL}, query;
        unsigned char hash[HASH_SIZE];
+       bool a_given = SERVER_CMD_OPT_GIVEN(ADD, ALL, pad->lpr);
+       bool f_given = SERVER_CMD_OPT_GIVEN(ADD, FORCE, pad->lpr);
+       bool l_given = SERVER_CMD_OPT_GIVEN(ADD, LAZY, pad->lpr);
+       bool v_given = SERVER_CMD_OPT_GIVEN(ADD, VERBOSE, pad->lpr);
 
        ret = guess_audio_format(path);
-       if (ret < 0 && !(pad->flags & ADD_FLAG_ALL)) {
+       if (ret < 0 && !a_given) {
                ret = 0;
                goto out_free;
        }
@@ -1827,8 +1838,8 @@ static int add_one_audio_file(const char *path, void *private_data)
        if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
                goto out_free;
        ret = 1;
-       if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */
-               if (pad->flags & ADD_FLAG_VERBOSE)
+       if (pb && l_given) { /* lazy is really cheap */
+               if (v_given)
                        send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT,
                                "lazy-ignore: %s\n", path);
                goto out_free;
@@ -1848,8 +1859,8 @@ static int add_one_audio_file(const char *path, void *private_data)
                goto out_unmap;
        /* Return success if we already know this file. */
        ret = 1;
-       if (pb && hs && hs == pb && !(pad->flags & ADD_FLAG_FORCE)) {
-               if (pad->flags & ADD_FLAG_VERBOSE)
+       if (pb && hs && hs == pb && !f_given) {
+               if (v_given)
                        send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT,
                                "%s exists, not forcing update\n", path);
                goto out_unmap;
@@ -1858,7 +1869,7 @@ static int add_one_audio_file(const char *path, void *private_data)
         * We won't recalculate the audio format info and the chunk table if
         * there is a hash sister and FORCE was not given.
         */
-       if (!hs || (pad->flags & ADD_FLAG_FORCE)) {
+       if (!hs || f_given) {
                ret = compute_afhi(path, map.data, map.size, fd, &afhi);
                if (ret < 0)
                        goto out_unmap;
@@ -1867,13 +1878,14 @@ static int add_one_audio_file(const char *path, void *private_data)
        }
        munmap(map.data, map.size);
        close(fd);
-       if (pad->flags & ADD_FLAG_VERBOSE) {
+       if (v_given) {
                send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT,
                        "adding %s\n", path);
                if (send_ret < 0)
                        goto out_free;
        }
-       save_add_callback_buffer(hash, path, afhi_ptr, pad->flags, format_num, &obj);
+       save_add_callback_buffer(hash, path, afhi_ptr, pad->slpr,
+               pad->slpr_size, format_num, &obj);
        /* Ask afs to consider this entry for adding. */
        ret = send_callback_request(com_add_callback, &obj,
                afs_cb_result_handler, pad->cc);
@@ -1892,46 +1904,30 @@ out_free:
        return send_ret;
 }
 
-int com_add(struct command_context *cc)
+static int com_add(struct command_context *cc, struct lls_parse_result *lpr)
 {
        int i, ret;
-       struct private_add_data pad = {.cc = cc, .flags = 0};
-
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-a")) {
-                       pad.flags |= ADD_FLAG_ALL;
-                       continue;
-               }
-               if (!strcmp(arg, "-l")) {
-                       pad.flags |= ADD_FLAG_LAZY;
-                       continue;
-               }
-               if (!strcmp(arg, "-f")) {
-                       pad.flags |= ADD_FLAG_FORCE;
-                       continue;
-               }
-               if (!strcmp(arg, "-v")) {
-                       pad.flags |= ADD_FLAG_VERBOSE;
-                       continue;
-               }
+       struct private_add_data pad = {.cc = cc, .lpr = lpr};
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADD);
+       unsigned num_inputs;
+       char *errctx;
+
+       ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
        }
-       if (cc->argc <= i)
-               return -E_AFT_SYNTAX;
-       for (; i < cc->argc; i++) {
+       ret = lls_serialize_parse_result(lpr, cmd, &pad.slpr, &pad.slpr_size);
+       assert(ret >= 0);
+       num_inputs = lls_num_inputs(lpr);
+       for (i = 0; i < num_inputs; i++) {
                char *path;
-               ret = verify_path(cc->argv[i], &path);
+               ret = verify_path(lls_input(i, lpr), &path);
                if (ret < 0) {
                        ret = send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s: %s\n",
-                               cc->argv[i], para_strerror(-ret));
+                               lls_input(i, lpr), para_strerror(-ret));
                        if (ret < 0)
-                               return ret;
+                               goto out;
                        continue;
                }
                if (ret == 1) /* directory */
@@ -1947,14 +1943,14 @@ int com_add(struct command_context *cc)
                }
                free(path);
        }
-       return 1;
+       ret = 1;
+out:
+       free(pad.slpr);
+       return ret;
 }
+EXPORT_SERVER_CMD_HANDLER(add);
 
-/**
- * Flags used by the touch command.
- *
- * \sa com_touch().
- */
+/** Flags used by the touch command. */
 enum touch_flags {
        /** Whether the \p FNM_PATHNAME flag should be passed to fnmatch(). */
        TOUCH_FLAG_FNM_PATHNAME = 1,
@@ -1962,33 +1958,26 @@ enum touch_flags {
        TOUCH_FLAG_VERBOSE = 2
 };
 
-/** Options used by com_touch(). */
-struct com_touch_options {
-       /** New num_played value. */
-       int32_t num_played;
-       /** New last played count. */
-       int64_t last_played;
-       /** New lyrics id. */
-       int32_t lyrics_id;
-       /** New image id. */
-       int32_t image_id;
-       /** New amplification value. */
-       int32_t amp;
-       /** Command line flags (see \ref touch_flags). */
-       unsigned flags;
-};
-
 static int touch_audio_file(__a_unused struct osl_table *table,
                struct osl_row *row, const char *name, void *data)
 {
        struct afs_callback_arg *aca = data;
-       struct com_touch_options *cto = aca->query.data;
+       bool v_given = SERVER_CMD_OPT_GIVEN(TOUCH, VERBOSE, aca->lpr);
+       const struct lls_opt_result *r_n, *r_l, *r_i, *r_y, *r_a;
+       int ret;
        struct osl_object obj;
        struct afs_info old_afsi, new_afsi;
-       int ret, no_options = cto->num_played < 0 && cto->last_played < 0 &&
-               cto->lyrics_id < 0 && cto->image_id < 0 && cto->amp < 0;
+       bool no_options;
        struct afsi_change_event_data aced;
 
+       r_n = SERVER_CMD_OPT_RESULT(TOUCH, NUMPLAYED, aca->lpr);
+       r_l = SERVER_CMD_OPT_RESULT(TOUCH, LASTPLAYED, aca->lpr);
+       r_i = SERVER_CMD_OPT_RESULT(TOUCH, IMAGE_ID, aca->lpr);
+       r_y = SERVER_CMD_OPT_RESULT(TOUCH, LYRICS_ID, aca->lpr);
+       r_a = SERVER_CMD_OPT_RESULT(TOUCH, AMP, aca->lpr);
+       no_options = !lls_opt_given(r_n) && !lls_opt_given(r_l) && !lls_opt_given(r_i)
+               && !lls_opt_given(r_y) && !lls_opt_given(r_a);
+
        ret = get_afsi_object_of_row(row, &obj);
        if (ret < 0) {
                para_printf(&aca->pbout, "cannot touch %s\n", name);
@@ -2003,23 +1992,23 @@ static int touch_audio_file(__a_unused struct osl_table *table,
        if (no_options) {
                new_afsi.num_played++;
                new_afsi.last_played = time(NULL);
-               if (cto->flags & TOUCH_FLAG_VERBOSE)
+               if (v_given)
                        para_printf(&aca->pbout, "%s: num_played = %u, "
                                "last_played = now()\n", name,
                                new_afsi.num_played);
        } else {
-               if (cto->flags & TOUCH_FLAG_VERBOSE)
+               if (lls_opt_given(r_l))
+                       new_afsi.last_played = lls_uint64_val(0, r_l);
+               if (lls_opt_given(r_n))
+                       new_afsi.num_played = lls_uint32_val(0, r_n);
+               if (lls_opt_given(r_i))
+                       new_afsi.image_id = lls_uint32_val(0, r_i);
+               if (lls_opt_given(r_y))
+                       new_afsi.lyrics_id = lls_uint32_val(0, r_y);
+               if (lls_opt_given(r_a))
+                       new_afsi.amp = lls_uint32_val(0, r_a);
+               if (v_given)
                        para_printf(&aca->pbout, "touching %s\n", name);
-               if (cto->lyrics_id >= 0)
-                       new_afsi.lyrics_id = cto->lyrics_id;
-               if (cto->image_id >= 0)
-                       new_afsi.image_id = cto->image_id;
-               if (cto->num_played >= 0)
-                       new_afsi.num_played = cto->num_played;
-               if (cto->last_played >= 0)
-                       new_afsi.last_played = cto->last_played;
-               if (cto->amp >= 0)
-                       new_afsi.amp = cto->amp;
        }
        save_afsi(&new_afsi, &obj); /* in-place update */
        aced.aft_row = row;
@@ -2029,134 +2018,73 @@ static int touch_audio_file(__a_unused struct osl_table *table,
 
 static int com_touch_callback(struct afs_callback_arg *aca)
 {
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(TOUCH);
+       bool p_given;
+       const struct lls_opt_result *r_i, *r_y;
        int ret;
-       struct com_touch_options *cto = aca->query.data;
        struct pattern_match_data pmd = {
                .table = audio_file_table,
                .loop_col_num = AFTCOL_HASH,
                .match_col_num = AFTCOL_PATH,
-               .patterns = {
-                       .data = (char *)aca->query.data
-                               + sizeof(struct com_touch_options),
-                       .size = aca->query.size
-                               - sizeof(struct com_touch_options)
-               },
                .data = aca,
                .action = touch_audio_file
        };
-       if (cto->image_id >= 0) {
-               ret = img_get_name_by_id(cto->image_id, NULL);
+
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       pmd.lpr = aca->lpr;
+
+       r_i = SERVER_CMD_OPT_RESULT(TOUCH, IMAGE_ID, aca->lpr);
+       if (lls_opt_given(r_i)) {
+               uint32_t id = lls_uint32_val(0, r_i);
+               ret = img_get_name_by_id(id, NULL);
                if (ret < 0) {
-                       para_printf(&aca->pbout, "invalid image ID: %d\n",
-                               cto->image_id);
+                       para_printf(&aca->pbout, "invalid image ID: %u\n", id);
                        return ret;
                }
        }
-       if (cto->lyrics_id >= 0) {
-               ret = lyr_get_name_by_id(cto->lyrics_id, NULL);
+       r_y = SERVER_CMD_OPT_RESULT(TOUCH, LYRICS_ID, aca->lpr);
+       if (lls_opt_given(r_y)) {
+               uint32_t id = lls_uint32_val(0, r_y);
+               ret = lyr_get_name_by_id(id, NULL);
                if (ret < 0) {
-                       para_printf(&aca->pbout, "invalid lyrics ID: %d\n",
-                               cto->lyrics_id);
+                       para_printf(&aca->pbout, "invalid lyrics ID: %u\n", id);
                        return ret;
                }
        }
-       if (cto->flags & TOUCH_FLAG_FNM_PATHNAME)
+       p_given = SERVER_CMD_OPT_GIVEN(TOUCH, PATHNAME_MATCH, aca->lpr);
+       if (p_given)
                pmd.fnmatch_flags |= FNM_PATHNAME;
        ret = for_each_matching_row(&pmd);
        if (ret >= 0 && pmd.num_matches == 0)
                ret = -E_NO_MATCH;
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-int com_touch(struct command_context *cc)
+static int com_touch(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       struct com_touch_options cto = {
-               .num_played = -1,
-               .last_played = -1,
-               .lyrics_id = -1,
-               .image_id = -1,
-               .amp = -1,
-       };
-       struct osl_object query = {.data = &cto, .size = sizeof(cto)};
-       int i, ret;
-
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(TOUCH);
+       int ret;
+       char *errctx;
 
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strncmp(arg, "-n=", 3)) {
-                       ret = para_atoi32(arg + 3, &cto.num_played);
-                       if (ret < 0)
-                               return ret;
-                       continue;
-               }
-               if (!strncmp(arg, "-l=", 3)) {
-                       ret = para_atoi64(arg + 3, &cto.last_played);
-                       if (ret < 0)
-                               return ret;
-                       continue;
-               }
-               if (!strncmp(arg, "-y=", 3)) {
-                       ret = para_atoi32(arg + 3, &cto.lyrics_id);
-                       if (ret < 0)
-                               return ret;
-                       continue;
-               }
-               if (!strncmp(arg, "-i=", 3)) {
-                       ret = para_atoi32(arg + 3, &cto.image_id);
-                       if (ret < 0)
-                               return ret;
-                       continue;
-               }
-               if (!strncmp(arg, "-a=", 3)) {
-                       int32_t val;
-                       ret = para_atoi32(arg + 3, &val);
-                       if (ret < 0)
-                               return ret;
-                       if (val < 0 || val > 255)
-                               return -ERRNO_TO_PARA_ERROR(EINVAL);
-                       cto.amp = val;
-                       continue;
-               }
-               if (!strcmp(arg, "-p")) {
-                       cto.flags |= TOUCH_FLAG_FNM_PATHNAME;
-                       continue;
-               }
-               if (!strcmp(arg, "-v")) {
-                       cto.flags |= TOUCH_FLAG_VERBOSE;
-                       continue;
-               }
-               break; /* non-option starting with dash */
+       ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
        }
-       if (i >= cc->argc)
-               return -E_AFT_SYNTAX;
-       return send_option_arg_callback_request(&query, cc->argc - i,
-               cc->argv + i, com_touch_callback, afs_cb_result_handler, cc);
-}
-
-/** Flags for com_rm(). */
-enum rm_flags {
-       /** -v */
-       RM_FLAG_VERBOSE = 1,
-       /** -f */
-       RM_FLAG_FORCE = 2,
-       /** -p */
-       RM_FLAG_FNM_PATHNAME = 4
-};
+       return send_lls_callback_request(com_touch_callback, cmd, lpr, cc);
+}
+EXPORT_SERVER_CMD_HANDLER(touch);
 
 static int remove_audio_file(__a_unused struct osl_table *table,
                struct osl_row *row, const char *name, void *data)
 {
        struct afs_callback_arg *aca = data;
-       uint32_t flags =*(uint32_t *)aca->query.data;
+       bool v_given = SERVER_CMD_OPT_GIVEN(RM, VERBOSE, aca->lpr);
        int ret;
 
-       if (flags & RM_FLAG_VERBOSE)
+       if (v_given)
                para_printf(&aca->pbout, "removing %s\n", name);
        ret = afs_event(AUDIO_FILE_REMOVE, &aca->pbout, row);
        if (ret < 0)
@@ -2169,95 +2097,63 @@ static int remove_audio_file(__a_unused struct osl_table *table,
 
 static int com_rm_callback(struct afs_callback_arg *aca)
 {
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RM);
        int ret;
-       uint32_t flags = *(uint32_t *)aca->query.data;
        struct pattern_match_data pmd = {
                .table = audio_file_table,
                .loop_col_num = AFTCOL_HASH,
                .match_col_num = AFTCOL_PATH,
-               .patterns = {.data = (char *)aca->query.data + sizeof(uint32_t),
-                       .size = aca->query.size - sizeof(uint32_t)},
                .data = aca,
                .action = remove_audio_file
        };
-       if (flags & RM_FLAG_FNM_PATHNAME)
+       bool v_given, p_given, f_given;
+
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       pmd.lpr = aca->lpr;
+       v_given = SERVER_CMD_OPT_GIVEN(RM, VERBOSE, aca->lpr);
+       p_given = SERVER_CMD_OPT_GIVEN(RM, PATHNAME_MATCH, aca->lpr);
+       f_given = SERVER_CMD_OPT_GIVEN(RM, FORCE, aca->lpr);
+
+       if (p_given)
                pmd.fnmatch_flags |= FNM_PATHNAME;
        ret = for_each_matching_row(&pmd);
        if (ret < 0)
                goto out;
        if (pmd.num_matches == 0) {
-               if (!(flags & RM_FLAG_FORCE))
+               if (!f_given)
                        ret = -E_NO_MATCH;
-       } else if (flags & RM_FLAG_VERBOSE)
+       } else if (v_given)
                para_printf(&aca->pbout, "removed %u file(s)\n",
                        pmd.num_matches);
 out:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
 /* TODO options: -r (recursive) */
-int com_rm(struct command_context *cc)
+static int com_rm(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       uint32_t flags = 0;
-       struct osl_object query = {.data = &flags, .size = sizeof(flags)};
-       int i;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RM);
+       char *errctx;
+       int ret;
 
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-f")) {
-                       flags |= RM_FLAG_FORCE;
-                       continue;
-               }
-               if (!strcmp(arg, "-p")) {
-                       flags |= RM_FLAG_FNM_PATHNAME;
-                       continue;
-               }
-               if (!strcmp(arg, "-v")) {
-                       flags |= RM_FLAG_VERBOSE;
-                       continue;
-               }
-               break;
+       ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
        }
-       if (i >= cc->argc)
-               return -E_AFT_SYNTAX;
-       return send_option_arg_callback_request(&query, cc->argc - i,
-               cc->argv + i, com_rm_callback, afs_cb_result_handler, cc);
+       return send_lls_callback_request(com_rm_callback, cmd, lpr, cc);
 }
-
-/**
- * Flags used by the cpsi command.
- *
- * \sa com_cpsi().
- */
-enum cpsi_flags {
-       /** Whether the lyrics id should be copied. */
-       CPSI_FLAG_COPY_LYRICS_ID = 1,
-       /** Whether the image id should be copied. */
-       CPSI_FLAG_COPY_IMAGE_ID = 2,
-       /** Whether the lastplayed time should be copied. */
-       CPSI_FLAG_COPY_LASTPLAYED = 4,
-       /** Whether the numplayed count should be copied. */
-       CPSI_FLAG_COPY_NUMPLAYED = 8,
-       /** Whether the attributes should be copied. */
-       CPSI_FLAG_COPY_ATTRIBUTES = 16,
-       /** Activates verbose mode. */
-       CPSI_FLAG_VERBOSE = 32,
-};
+EXPORT_SERVER_CMD_HANDLER(rm);
 
 /** Data passed to the action handler of com_cpsi(). */
 struct cpsi_action_data {
-       /** command line flags (see \ref cpsi_flags). */
-       unsigned flags;
        /** Values are copied from here. */
        struct afs_info source_afsi;
        /** What was passed to com_cpsi_callback(). */
        struct afs_callback_arg *aca;
+       bool copy_all;
 };
 
 static int copy_selector_info(__a_unused struct osl_table *table,
@@ -2268,6 +2164,14 @@ static int copy_selector_info(__a_unused struct osl_table *table,
        int ret;
        struct afs_info old_afsi, target_afsi;
        struct afsi_change_event_data aced;
+       bool a_given, y_given, i_given, l_given, n_given, v_given;
+
+       a_given = SERVER_CMD_OPT_GIVEN(CPSI, ATTRIBUTE_BITMAP, cad->aca->lpr);
+       y_given = SERVER_CMD_OPT_GIVEN(CPSI, LYRICS_ID, cad->aca->lpr);
+       i_given = SERVER_CMD_OPT_GIVEN(CPSI, IMAGE_ID, cad->aca->lpr);
+       l_given = SERVER_CMD_OPT_GIVEN(CPSI, LASTPLAYED, cad->aca->lpr);
+       n_given = SERVER_CMD_OPT_GIVEN(CPSI, NUMPLAYED, cad->aca->lpr);
+       v_given = SERVER_CMD_OPT_GIVEN(CPSI, VERBOSE, cad->aca->lpr);
 
        ret = get_afsi_object_of_row(row, &target_afsi_obj);
        if (ret < 0)
@@ -2276,18 +2180,18 @@ static int copy_selector_info(__a_unused struct osl_table *table,
        if (ret < 0)
                return ret;
        old_afsi = target_afsi;
-       if (cad->flags & CPSI_FLAG_COPY_LYRICS_ID)
+       if (cad->copy_all || y_given)
                target_afsi.lyrics_id = cad->source_afsi.lyrics_id;
-       if (cad->flags & CPSI_FLAG_COPY_IMAGE_ID)
+       if (cad->copy_all || i_given)
                target_afsi.image_id = cad->source_afsi.image_id;
-       if (cad->flags & CPSI_FLAG_COPY_LASTPLAYED)
+       if (cad->copy_all || l_given)
                target_afsi.last_played = cad->source_afsi.last_played;
-       if (cad->flags & CPSI_FLAG_COPY_NUMPLAYED)
+       if (cad->copy_all || n_given)
                target_afsi.num_played = cad->source_afsi.num_played;
-       if (cad->flags & CPSI_FLAG_COPY_ATTRIBUTES)
+       if (cad->copy_all || a_given)
                target_afsi.attributes = cad->source_afsi.attributes;
        save_afsi(&target_afsi, &target_afsi_obj); /* in-place update */
-       if (cad->flags & CPSI_FLAG_VERBOSE)
+       if (v_given)
                para_printf(&cad->aca->pbout, "copied afsi to %s\n", name);
        aced.aft_row = row;
        aced.old_afsi = &old_afsi;
@@ -2296,86 +2200,60 @@ static int copy_selector_info(__a_unused struct osl_table *table,
 
 static int com_cpsi_callback(struct afs_callback_arg *aca)
 {
-       struct cpsi_action_data cad = {
-               .flags = *(unsigned *)aca->query.data,
-               .aca = aca
-       };
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(CPSI);
+       bool a_given, y_given, i_given, l_given, n_given, v_given;
+       struct cpsi_action_data cad = {.aca = aca};
        int ret;
-       char *source_path = (char *)aca->query.data + sizeof(cad.flags);
        struct pattern_match_data pmd = {
                .table = audio_file_table,
                .loop_col_num = AFTCOL_HASH,
                .match_col_num = AFTCOL_PATH,
-               .patterns = {.data = source_path + strlen(source_path) + 1,
-                       .size = aca->query.size - sizeof(cad.flags)
-                               - strlen(source_path) - 1},
+               .input_skip = 1, /* skip first argument (source file) */
                .data = &cad,
                .action = copy_selector_info
        };
 
-       ret = get_afsi_of_path(source_path, &cad.source_afsi);
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       pmd.lpr = aca->lpr;
+
+       a_given = SERVER_CMD_OPT_GIVEN(CPSI, ATTRIBUTE_BITMAP, aca->lpr);
+       y_given = SERVER_CMD_OPT_GIVEN(CPSI, LYRICS_ID, aca->lpr);
+       i_given = SERVER_CMD_OPT_GIVEN(CPSI, IMAGE_ID, aca->lpr);
+       l_given = SERVER_CMD_OPT_GIVEN(CPSI, LASTPLAYED, aca->lpr);
+       n_given = SERVER_CMD_OPT_GIVEN(CPSI, NUMPLAYED, aca->lpr);
+       v_given = SERVER_CMD_OPT_GIVEN(CPSI, VERBOSE, aca->lpr);
+       cad.copy_all = !a_given && !y_given && !i_given && !l_given && !n_given;
+
+       ret = get_afsi_of_path(lls_input(0, aca->lpr), &cad.source_afsi);
        if (ret < 0)
                goto out;
        ret = for_each_matching_row(&pmd);
        if (ret < 0)
                goto out;
        if (pmd.num_matches > 0) {
-               if (cad.flags & CPSI_FLAG_VERBOSE)
+               if (v_given)
                        para_printf(&aca->pbout, "updated afsi of %u file(s)\n",
                                pmd.num_matches);
        } else
                ret = -E_NO_MATCH;
 out:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-int com_cpsi(struct command_context *cc)
+static int com_cpsi(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       unsigned flags = 0;
-       int i;
-       struct osl_object options = {.data = &flags, .size = sizeof(flags)};
-
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-y")) {
-                       flags |= CPSI_FLAG_COPY_LYRICS_ID;
-                       continue;
-               }
-               if (!strcmp(arg, "-i")) {
-                       flags |= CPSI_FLAG_COPY_IMAGE_ID;
-                       continue;
-               }
-               if (!strcmp(arg, "-l")) {
-                       flags |= CPSI_FLAG_COPY_LASTPLAYED;
-                       continue;
-               }
-               if (!strcmp(arg, "-n")) {
-                       flags |= CPSI_FLAG_COPY_NUMPLAYED;
-                       continue;
-               }
-               if (!strcmp(arg, "-a")) {
-                       flags |= CPSI_FLAG_COPY_ATTRIBUTES;
-                       continue;
-               }
-               if (!strcmp(arg, "-v")) {
-                       flags |= CPSI_FLAG_VERBOSE;
-                       continue;
-               }
-               break;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(CPSI);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 2, INT_MAX, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
        }
-       if (i + 1 >= cc->argc) /* need at least source file and pattern */
-               return -E_AFT_SYNTAX;
-       if (!(flags & ~CPSI_FLAG_VERBOSE)) /* no copy flags given */
-               flags = ~(unsigned)CPSI_FLAG_VERBOSE | flags;
-       return send_option_arg_callback_request(&options, cc->argc - i,
-               cc->argv + i, com_cpsi_callback, afs_cb_result_handler, cc);
+       return send_lls_callback_request(com_cpsi_callback, cmd, lpr, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(cpsi);
 
 struct change_atts_data {
        uint64_t add_mask, del_mask;
@@ -2409,9 +2287,8 @@ static int change_atts(__a_unused struct osl_table *table,
 
 static int com_setatt_callback(struct afs_callback_arg *aca)
 {
-       char *p;
-       int ret;
-       size_t len;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SETATT);
+       int i, ret;
        struct change_atts_data cad = {.aca = aca};
        struct pattern_match_data pmd = {
                .table = audio_file_table,
@@ -2421,27 +2298,36 @@ static int com_setatt_callback(struct afs_callback_arg *aca)
                .data = &cad,
                .action = change_atts
        };
+       unsigned num_inputs;
 
-       for (
-               p = aca->query.data;
-               p < (char *)aca->query.data + aca->query.size;
-               p += len + 1
-       ) {
-               char c;
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       pmd.lpr = aca->lpr;
+
+       num_inputs = lls_num_inputs(aca->lpr);
+       for (i = 0; i < num_inputs; i++) {
                unsigned char bitnum;
                uint64_t one = 1;
+               const char *arg = lls_input(i, aca->lpr);
+               char c, *p;
+               size_t len = strlen(arg);
 
-               len = strlen(p);
                ret = -E_ATTR_SYNTAX;
                if (len == 0)
                        goto out;
-               c = p[len - 1];
-               if (c != '+' && c != '-')
-                       break;
+               c = arg[len - 1];
+               if (c != '+' && c != '-') {
+                       if (cad.add_mask == 0 && cad.del_mask == 0)
+                               goto out; /* no attribute modifier given */
+                       goto set_atts;
+               }
+               p = para_malloc(len);
+               memcpy(p, arg, len - 1);
                p[len - 1] = '\0';
                ret = get_attribute_bitnum_by_name(p, &bitnum);
+               free(p);
                if (ret < 0) {
-                       para_printf(&aca->pbout, "attribute not found: %s\n", p);
+                       para_printf(&aca->pbout, "invalid argument: %s\n", arg);
                        goto out;
                }
                if (c == '+')
@@ -2449,29 +2335,32 @@ static int com_setatt_callback(struct afs_callback_arg *aca)
                else
                        cad.del_mask |= (one << bitnum);
        }
+       /* no pattern given */
        ret = -E_ATTR_SYNTAX;
-       if (!cad.add_mask && !cad.del_mask)
-               goto out;
-       pmd.patterns.data = p;
-       if (p >= (char *)aca->query.data + aca->query.size)
-               goto out;
-       pmd.patterns.size = (char *)aca->query.data + aca->query.size - p;
+       goto out;
+set_atts:
+       pmd.input_skip = i;
        ret = for_each_matching_row(&pmd);
-       if (ret < 0)
-               goto out;
-       if (pmd.num_matches == 0)
+       if (ret >= 0 && pmd.num_matches == 0)
                ret = -E_NO_MATCH;
 out:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-int com_setatt(struct command_context *cc)
+static int com_setatt(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       if (cc->argc < 3)
-               return -E_ATTR_SYNTAX;
-       return send_standard_callback_request(cc->argc - 1, cc->argv + 1,
-               com_setatt_callback, afs_cb_result_handler, cc);
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SETATT);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 2, INT_MAX, &errctx));
+
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       return send_lls_callback_request(com_setatt_callback, cmd, lpr, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(setatt);
 
 static int afs_stat_callback(struct afs_callback_arg *aca)
 {
@@ -2547,8 +2436,6 @@ static int check_audio_file(struct osl_row *row, void *data)
  * \param aca Only ->pbout is used for diagnostics.
  *
  * \return Standard. Inconsistencies are reported but not regarded as an error.
- *
- * \sa com_check().
  */
 int aft_check_callback(struct afs_callback_arg *aca)
 {
@@ -2612,28 +2499,30 @@ int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb)
        return audio_file_loop(&acad, check_atts_of_audio_file);
 }
 
-/**
- * Close the audio file table.
- *
- * \param flags Usual flags that are passed to osl_close_table().
- *
- * \sa osl_close_table().
+/*
+ * This sets audio_file_table to NULL, but leaves current_aft_row unmodified,
+ * though stale (pointing to unmapped memory). If the table is being closed
+ * because we received SIGHUP, the table will be reopened after the config file
+ * has been reloaded. We remember the hash of the current audio file here so
+ * that aft_open() can initialize current_aft_row by looking up the saved hash.
  */
 static void aft_close(void)
 {
+       int ret;
+       unsigned char *p;
+
+       if (current_aft_row) {
+               ret = get_hash_of_row(current_aft_row, &p);
+               if (ret < 0) {
+                       PARA_WARNING_LOG("hash lookup failure\n");
+                       current_aft_row = NULL;
+               } else
+                       memcpy(current_hash, p, HASH_SIZE);
+       }
        osl_close_table(audio_file_table, OSL_MARK_CLEAN);
        audio_file_table = NULL;
 }
 
-/**
- * Open the audio file table.
- *
- * \param dir The database directory.
- *
- * \return Standard.
- *
- * \sa osl_open_table().
- */
 static int aft_open(const char *dir)
 {
        int ret;
@@ -2644,12 +2533,27 @@ static int aft_open(const char *dir)
                unsigned num;
                osl_get_num_rows(audio_file_table, &num);
                PARA_INFO_LOG("audio file table contains %u files\n", num);
-               return ret;
+               if (!current_aft_row) {
+                       PARA_DEBUG_LOG("no current aft row\n");
+                       return 1;
+               }
+               /* SIGHUP case, update current_aft_row */
+               ret = aft_get_row_of_hash(current_hash, &current_aft_row);
+               if (ret < 0) { /* not fatal */
+                       PARA_WARNING_LOG("current hash lookup failure: %s\n",
+                               para_strerror(-ret));
+                       current_aft_row = NULL;
+                       return 1;
+               }
+               PARA_NOTICE_LOG("current audio file hash lookup: success\n");
+               return 1;
        }
-       PARA_NOTICE_LOG("failed to open audio file table\n");
        audio_file_table = NULL;
-       if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT))
+       if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) {
+               PARA_WARNING_LOG("no audio file table\n");
                return 1;
+       }
+       PARA_NOTICE_LOG("failed to open audio file table\n");
        return ret;
 }
 
@@ -2700,6 +2604,17 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb,
                status_item_ls_data.afsi.last_played = old_last_played;
                make_status_items();
                return 1;
+       } case AUDIO_FILE_RENAME: {
+               char *path;
+               if (data != current_aft_row)
+                       return 0;
+               ret = get_audio_file_path_of_row(current_aft_row, &path);
+               if (ret < 0)
+                       return ret;
+               free(status_item_ls_data.path);
+               status_item_ls_data.path = para_strdup(path);
+               make_status_items();
+               return 1;
        } case AFHI_CHANGE: {
                if (data != current_aft_row)
                        return 0;
@@ -2708,6 +2623,27 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb,
                        return ret;
                make_status_items();
                return 1;
+       } case AUDIO_FILE_REMOVE: {
+               if (data == current_aft_row)
+                       current_aft_row = NULL;
+               return 0;
+       }
+       case BLOB_RENAME:
+       case BLOB_REMOVE:
+       case BLOB_ADD: {
+               /*
+                * These events are rare. We don't bother to check whether the
+                * current status items are affected and simply recreate them
+                * whenever an audio file is open.
+                */
+               if (!current_aft_row)
+                       return 0;
+               ret = get_afhi_of_row(current_aft_row,
+                       &status_item_ls_data.afhi);
+               if (ret < 0)
+                       return ret;
+               make_status_items();
+               return 0;
        } default:
                return 0;
        }
index 3adee929a804434fcea1999a439819dc85c66936..1d81e5d9dbabbcef772d182eabe6a03ba5a51915 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file alsa_mix.c The ALSA mixer plugin. */
 
@@ -216,19 +212,13 @@ static int alsa_mix_set(struct mixer_handle *h, int val)
        return 1;
 }
 
-/**
- * The init function of the ALSA mixer.
- *
- * \param self The structure to initialize.
- *
- * \sa struct \ref mixer, \ref oss_mix_init().
- */
-void alsa_mix_init(struct mixer *self)
-{
-       self->open = alsa_mix_open;
-       self->get_channels = alsa_mix_get_channels;
-       self->set_channel = alsa_mix_set_channel;
-       self->close = alsa_mix_close;
-       self->get = alsa_mix_get;
-       self->set = alsa_mix_set;
-}
+/** The mixer operations for the ALSA mixer. */
+const struct mixer alsa_mixer = {
+       .name = "alsa",
+       .open = alsa_mix_open,
+       .get_channels = alsa_mix_get_channels,
+       .set_channel = alsa_mix_set_channel,
+       .close = alsa_mix_close,
+       .get = alsa_mix_get,
+       .set = alsa_mix_set
+};
index fd3b404c0359c1a571255b1ac7265b0ee0faef41..bc06fc315bc51fb3fc1fa9729ae429b47a70d4b4 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file alsa_write.c paraslash's alsa output plugin */
 
 #include <regex.h>
 #include <sys/types.h>
 #include <alsa/asoundlib.h>
+#include <lopsub.h>
 
+#include "write_cmd.lsg.h"
 #include "para.h"
 #include "fd.h"
 #include "string.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "write.h"
-#include "write_common.h"
-#include "alsa_write.cmdline.h"
 #include "error.h"
 
 /** Data specific to the alsa writer. */
@@ -71,22 +66,22 @@ static snd_pcm_format_t get_alsa_pcm_format(enum sample_format sf)
 }
 
 /* Install PCM software and hardware configuration. */
-static int alsa_init(struct private_alsa_write_data *pad,
-               struct alsa_write_args_info *conf)
+static int alsa_init(struct writer_node *wn)
 {
+       struct private_alsa_write_data *pad = wn->private_data;
        snd_pcm_hw_params_t *hwparams = NULL;
        snd_pcm_sw_params_t *swparams = NULL;
        snd_pcm_uframes_t start_threshold, stop_threshold;
        snd_pcm_uframes_t buffer_size, period_size;
        snd_output_t *output_log;
        int ret;
-       const char *msg;
+       const char *msg, *dev = WRITE_CMD_OPT_STRING_VAL(ALSA, DEVICE, wn->lpr);
        unsigned period_time;
 
-       PARA_INFO_LOG("opening %s\n", conf->device_arg);
+       PARA_INFO_LOG("opening %s\n", dev);
        msg = "unable to open pcm";
-       ret = snd_pcm_open(&pad->handle, conf->device_arg,
-               SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+       ret = snd_pcm_open(&pad->handle, dev, SND_PCM_STREAM_PLAYBACK,
+               SND_PCM_NONBLOCK);
        if (ret < 0)
                goto fail;
        ret = snd_pcm_hw_params_malloc(&hwparams);
@@ -116,7 +111,8 @@ static int alsa_init(struct private_alsa_write_data *pad,
        if (ret < 0)
                goto fail;
        /* alsa wants microseconds */
-       pad->buffer_time = conf->buffer_time_arg * 1000;
+       pad->buffer_time = 1000U * WRITE_CMD_OPT_UINT32_VAL(ALSA, BUFFER_TIME,
+               wn->lpr);
        msg = "could not set buffer time";
        ret = snd_pcm_hw_params_set_buffer_time_near(pad->handle, hwparams,
                &pad->buffer_time, NULL);
@@ -270,6 +266,8 @@ static int alsa_write_post_select(__a_unused struct sched *s, void *context)
                goto err;
 again:
        ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
+       if (ret < 0)
+               goto err;
        if (ret == 0)
                return 0;
        btr_merge(btrn, wn->min_iqs);
@@ -295,7 +293,7 @@ again:
 
                if (bytes == 0) /* no data available */
                        return 0;
-               pad = para_calloc(sizeof(*pad));
+               pad = wn->private_data = para_calloc(sizeof(*pad));
                get_btr_sample_rate(btrn, &val);
                pad->sample_rate = val;
                get_btr_channels(btrn, &val);
@@ -305,12 +303,12 @@ again:
 
                PARA_INFO_LOG("%u channel(s), %uHz\n", pad->channels,
                        pad->sample_rate);
-               ret = alsa_init(pad, wn->conf);
+               ret = alsa_init(wn);
                if (ret < 0) {
-                       free(pad);
+                       free(wn->private_data);
+                       wn->private_data = NULL;
                        goto err;
                }
-               wn->private_data = pad;
                wn->min_iqs = pad->bytes_per_frame;
                goto again;
        }
@@ -340,37 +338,9 @@ err:
        return ret;
 }
 
-__malloc static void *alsa_parse_config_or_die(int argc, char **argv)
-{
-       struct alsa_write_args_info *conf = para_calloc(sizeof(*conf));
-
-       /* exits on errors */
-       alsa_write_cmdline_parser(argc, argv, conf);
-       return conf;
-}
-
-static void alsa_free_config(void *conf)
-{
-       alsa_write_cmdline_parser_free(conf);
-}
-
-/**
- * The init function of the alsa writer.
- *
- * \param w Pointer to the writer to initialize.
- *
- * \sa struct \ref writer.
- */
-void alsa_write_init(struct writer *w)
-{
-       struct alsa_write_args_info dummy;
+struct writer lsg_write_cmd_com_alsa_user_data = {
 
-       alsa_write_cmdline_parser_init(&dummy);
-       w->close = alsa_close;
-       w->pre_select = alsa_write_pre_select;
-       w->post_select = alsa_write_post_select;
-       w->parse_config_or_die = alsa_parse_config_or_die;
-       w->free_config = alsa_free_config;
-       w->help = (struct ggo_help)DEFINE_GGO_HELP(alsa_write);
-       alsa_write_cmdline_parser_free(&dummy);
-}
+       .pre_select = alsa_write_pre_select,
+       .post_select = alsa_write_post_select,
+       .close = alsa_close,
+};
index 5193d7c166d9345ced27899e38714ddd5d393fcf..61b1653ea069ce479b6b609da5703fc57bc717c1 100644 (file)
@@ -1,18 +1,14 @@
-/*
- * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file amp_filter.c Paraslash's amplify filter. */
 
 #include <regex.h>
+#include <lopsub.h>
 
+#include "filter_cmd.lsg.h"
 #include "para.h"
-#include "amp_filter.cmdline.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
@@ -31,33 +27,18 @@ static void amp_close(struct filter_node *fn)
        free(fn->private_data);
 }
 
-static int amp_parse_config(int argc, char **argv, void **config)
-{
-       struct amp_filter_args_info *conf = para_calloc(sizeof(*conf));
-       int ret;
-
-       amp_filter_cmdline_parser(argc, argv, conf);
-       ret = -ERRNO_TO_PARA_ERROR(EINVAL);
-       if (conf->amp_arg < 0)
-               goto err;
-       *config = conf;
-       return 1;
-err:
-       free(conf);
-       return ret;
-}
-
 static void amp_open(struct filter_node *fn)
 {
        struct private_amp_data *pad = para_calloc(sizeof(*pad));
-       struct amp_filter_args_info *conf = fn->conf;
+       unsigned given = FILTER_CMD_OPT_GIVEN(AMP, AMP, fn->lpr);
+       uint32_t amp_arg = FILTER_CMD_OPT_UINT32_VAL(AMP, AMP, fn->lpr);
 
        fn->private_data = pad;
        fn->min_iqs = 2;
-       if (!conf->amp_given && stat_item_values[SI_AMPLIFICATION])
-               sscanf(stat_item_values[SI_AMPLIFICATION], "%u", &pad->amp);
+       if (!given && stat_item_values[SI_amplification])
+               sscanf(stat_item_values[SI_amplification], "%u", &pad->amp);
        else
-               pad->amp = conf->amp_arg;
+               pad->amp = amp_arg;
        PARA_INFO_LOG("amplification: %u (scaling factor: %1.2f)\n",
                pad->amp, pad->amp / 64.0 + 1.0);
 }
@@ -116,26 +97,9 @@ err:
        return ret;
 }
 
-static void amp_free_config(void *conf)
-{
-       amp_filter_cmdline_parser_free(conf);
-}
-
-/**
- * The init function of the amplify filter.
- *
- * \param f Pointer to the struct to initialize.
- */
-void amp_filter_init(struct filter *f)
-{
-       struct amp_filter_args_info dummy;
-
-       amp_filter_cmdline_parser_init(&dummy);
-       f->open = amp_open;
-       f->close = amp_close;
-       f->pre_select = generic_filter_pre_select;
-       f->post_select = amp_post_select;
-       f->parse_config = amp_parse_config;
-       f->free_config = amp_free_config;
-       f->help = (struct ggo_help)DEFINE_GGO_HELP(amp_filter);
-}
+const struct filter lsg_filter_cmd_com_amp_user_data = {
+       .open = amp_open,
+       .close = amp_close,
+       .pre_select = generic_filter_pre_select,
+       .post_select = amp_post_select,
+};
index 82d98f3ee8cf6c0491e1820ee32b63c8216abac2..447dea84c00a468330fe8229bfd70b74292264bd 100644 (file)
@@ -1,25 +1,20 @@
-/*
- * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file ao_write.c Paraslash's libao output plugin. */
 
 #include <pthread.h>
 #include <ao/ao.h>
 #include <regex.h>
+#include <lopsub.h>
 
+#include "write_cmd.lsg.h"
 #include "para.h"
 #include "fd.h"
 #include "string.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "write.h"
-#include "write_common.h"
-#include "ao_write.cmdline.h"
 #include "error.h"
 
 struct private_aow_data {
@@ -44,6 +39,7 @@ static void aow_close(struct writer_node *wn)
        ao_close(pawd->dev);
        free(pawd);
        wn->private_data = NULL;
+       ao_shutdown();
 }
 
 static void aow_pre_select(struct sched *s, void *context)
@@ -91,7 +87,7 @@ static int aow_set_sample_format(unsigned sample_rate, unsigned channels,
                case SF_U8:
                case SF_U16_LE:
                case SF_U16_BE:
-                       return -E_AO_BAD_SAMPLE_FORMAT;
+                       return -E_BAD_SAMPLE_FORMAT;
                case SF_S8:
                        /* no need to set byte_format */
                        result->bits = 8;
@@ -147,6 +143,38 @@ static int aow_open_device(int id, ao_sample_format *asf, ao_option *options,
        return -E_AO_OPEN_LIVE;
 }
 
+static void aow_show_drivers(void)
+{
+       int i, j, num_drivers;
+       ao_info **driver_list;
+
+       PARA_DEBUG_LOG("libao drivers available on this host:\n");
+       PARA_DEBUG_LOG("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
+
+       driver_list = ao_driver_info_list(&num_drivers);
+
+       for (i = 0; i < num_drivers; i++) {
+               ao_info *info = driver_list[i];
+               char *keys = NULL, *tmp = NULL;
+
+               if (info->type == AO_TYPE_FILE)
+                       continue;
+               PARA_DEBUG_LOG("name: %s: %s\n", info->short_name, info->name);
+               PARA_DEBUG_LOG("priority: %d\n", info->priority);
+               for (j = 0; j < info->option_count; j++) {
+                       tmp = make_message("%s%s%s", keys? keys : "",
+                               keys? ", " : "",
+                               info->options[j]);
+                       free(keys);
+                       keys = tmp;
+               }
+               PARA_DEBUG_LOG("keys: %s\n", keys? keys : "[none]");
+               free(keys);
+               PARA_DEBUG_LOG("comment: %s\n", info->comment?
+                       info->comment : "[none]");
+       }
+}
+
 static int aow_init(struct writer_node *wn, unsigned sample_rate,
                unsigned channels, int sample_format)
 {
@@ -154,12 +182,15 @@ static int aow_init(struct writer_node *wn, unsigned sample_rate,
        ao_option *aoo = NULL;
        ao_sample_format asf;
        ao_info *info;
+       const struct lls_opt_result *r;
+       unsigned n;
        struct private_aow_data *pawd = para_malloc(sizeof(*pawd));
-       struct ao_write_args_info *conf = wn->conf;
 
-       if (conf->driver_given) {
+       ao_initialize();
+       aow_show_drivers();
+       if (WRITE_CMD_OPT_GIVEN(AO, DRIVER, wn->lpr)) {
                ret = -E_AO_BAD_DRIVER;
-               id = ao_driver_id(conf->driver_arg);
+               id = ao_driver_id(WRITE_CMD_OPT_STRING_VAL(AO, DRIVER, wn->lpr));
        } else {
                ret = -E_AO_DEFAULT_DRIVER;
                id = ao_default_driver_id();
@@ -173,8 +204,11 @@ static int aow_init(struct writer_node *wn, unsigned sample_rate,
                goto fail;
        }
        PARA_INFO_LOG("using %s driver\n", info->short_name);
-       for (i = 0; i < conf->ao_option_given; i++) {
-               char *o = para_strdup(conf->ao_option_arg[i]), *value;
+       r = WRITE_CMD_OPT_RESULT(AO, AO_OPTION, wn->lpr);
+       n = lls_opt_given(r);
+       for (i = 0; i < n; i++) {
+               char *o = para_strdup(lls_string_val(i, r));
+               char *value;
 
                ret = -E_AO_BAD_OPTION;
                value = strchr(o, ':');
@@ -210,7 +244,7 @@ fail:
        return ret;
 }
 
-__noreturn static void *aow_play(void *priv)
+static void *aow_play(void *priv)
 {
        struct writer_node *wn = priv;
        struct private_aow_data *pawd = wn->private_data;
@@ -379,78 +413,9 @@ out:
        return ret;
 }
 
-__malloc static void *aow_parse_config_or_die(int argc, char **argv)
-{
-       struct ao_write_args_info *conf = para_calloc(sizeof(*conf));
-
-       /* exits on errors */
-       ao_write_cmdline_parser(argc, argv, conf);
-       return conf;
-}
-
-static void aow_free_config(void *conf)
-{
-       ao_write_cmdline_parser_free(conf);
-}
-
-/**
- * The init function of the ao writer.
- *
- * \param w Pointer to the writer to initialize.
- *
- * \sa struct writer.
- */
-void ao_write_init(struct writer *w)
-{
-       struct ao_write_args_info dummy;
-       int i, j, num_drivers, num_lines;
-       ao_info **driver_list;
-       char **dh; /* detailed help */
-
-       ao_write_cmdline_parser_init(&dummy);
-       w->close = aow_close;
-       w->pre_select = aow_pre_select;
-       w->post_select = aow_post_select;
-       w->parse_config_or_die = aow_parse_config_or_die;
-       w->free_config = aow_free_config;
-       w->help = (struct ggo_help)DEFINE_GGO_HELP(ao_write);
-       /* create detailed help containing all supported drivers/options */
-       for (i = 0; ao_write_args_info_detailed_help[i]; i++)
-               ; /* nothing */
-       num_lines = i;
-       dh = para_malloc((num_lines + 3) * sizeof(char *));
-       for (i = 0; i < num_lines; i++)
-               dh[i] = para_strdup(ao_write_args_info_detailed_help[i]);
-       dh[num_lines++] = para_strdup("libao drivers available on this host:");
-       dh[num_lines++] = para_strdup("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
-
-       ao_initialize();
-       driver_list = ao_driver_info_list(&num_drivers);
-
-       for (i = 0; i < num_drivers; i++) {
-               ao_info *info = driver_list[i];
-               char *keys = NULL, *tmp = NULL;
-
-               if (info->type == AO_TYPE_FILE)
-                       continue;
-               for (j = 0; j < info->option_count; j++) {
-                       tmp = make_message("%s%s%s", keys? keys : "",
-                               keys? ", " : "",
-                               info->options[j]);
-                       free(keys);
-                       keys = tmp;
-               }
-               dh = para_realloc(dh, (num_lines + 6) * sizeof(char *));
-               dh[num_lines++] = make_message("%s: %s", info->short_name, info->name);
-               dh[num_lines++] = make_message("priority: %d", info->priority);
-               dh[num_lines++] = make_message("keys: %s", keys? keys : "[none]");
-               dh[num_lines++] = make_message("comment: %s", info->comment?
-                       info->comment : "[none]");
-               dh[num_lines++] = para_strdup(NULL);
-               free(keys);
-       }
-       dh[num_lines] = NULL;
-       w->help.detailed_help = (const char **)dh;
-       ao_write_cmdline_parser_free(&dummy);
-}
+struct writer lsg_write_cmd_com_ao_user_data = {
+       .close = aow_close,
+       .pre_select = aow_pre_select,
+       .post_select = aow_post_select,
+};
 
index 4cb2982860de78dd26b9fc55ad552c73a7ca1b9f..f7091727bc5b62518f91436de9428ba6c14b5a86 100644 (file)
@@ -1,14 +1,12 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file attribute.c Attribute handling functions. */
 
 #include <regex.h>
 #include <osl.h>
+#include <lopsub.h>
 
+#include "server_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "crypt.h"
@@ -106,30 +104,16 @@ int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum)
        return 1;
 }
 
-/**
- * Flags used by the lsatt command.
- *
- * \param \sa com_lsatt().
- */
-enum lsatt_flags {
-       /** Whether "-a" was given for the lsatt command. */
-       LSATT_FLAG_SORT_BY_ID = 1,
-       /** Whether "-l" was given for the lsatt command. */
-       LSATT_FLAG_LONG = 2,
-       /** Reverse sort order. */
-       LSATT_FLAG_REVERSE = 4
-};
-
 /** Data passed to the action function of lsatt */
 static int print_attribute(struct osl_table *table, struct osl_row *row,
                const char *name, void *data)
 {
        struct afs_callback_arg *aca = data;
-       unsigned flags = *(unsigned *)aca->query.data;
+       bool l_given = SERVER_CMD_OPT_GIVEN(LSATT, LONG, aca->lpr);
        struct osl_object bitnum_obj;
        int ret;
 
-       if (!(flags & LSATT_FLAG_LONG)) {
+       if (!l_given) {
                para_printf(&aca->pbout, "%s\n", name);
                return 1;
        }
@@ -145,21 +129,27 @@ static int print_attribute(struct osl_table *table, struct osl_row *row,
 
 static int com_lsatt_callback(struct afs_callback_arg *aca)
 {
-       unsigned flags = *(unsigned *)aca->query.data;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LSATT);
+       bool i_given, r_given;
        int ret;
        struct pattern_match_data pmd = {
                .table = attribute_table,
                .loop_col_num = ATTCOL_NAME,
                .match_col_num = ATTCOL_NAME,
-               .patterns = {.data = (char *)aca->query.data + sizeof(flags),
-                       .size = aca->query.size - sizeof(flags)},
                .pm_flags = PM_NO_PATTERN_MATCHES_EVERYTHING,
                .data = aca,
                .action = print_attribute
        };
-       if (flags & LSATT_FLAG_SORT_BY_ID)
+
+       ret = lls(lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr));
+       assert(ret >= 0);
+       pmd.lpr = aca->lpr;
+       i_given = SERVER_CMD_OPT_GIVEN(LSATT, ID_SORT, aca->lpr);
+       r_given = SERVER_CMD_OPT_GIVEN(LSATT, REVERSE, aca->lpr);
+
+       if (i_given)
                pmd.loop_col_num = ATTCOL_BITNUM;
-       if (flags & LSATT_FLAG_REVERSE)
+       if (r_given)
                pmd.pm_flags |= PM_REVERSE_LOOP;
        ret = for_each_matching_row(&pmd);
        if (ret < 0)
@@ -167,70 +157,49 @@ static int com_lsatt_callback(struct afs_callback_arg *aca)
        if (pmd.num_matches == 0)
                ret = -E_NO_MATCH;
 out:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-int com_lsatt(struct command_context *cc)
+static int com_lsatt(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       unsigned flags = 0;
-       struct osl_object options = {.data = &flags, .size = sizeof(flags)};
-       int i;
-
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-i")) {
-                       flags |= LSATT_FLAG_SORT_BY_ID;
-                       continue;
-               }
-               if (!strcmp(arg, "-l")) {
-                       flags |= LSATT_FLAG_LONG;
-                       continue;
-               }
-               if (!strcmp(arg, "-r")) {
-                       flags |= LSATT_FLAG_REVERSE;
-                       continue;
-               }
-       }
-       return send_option_arg_callback_request(&options, cc->argc - i, cc->argv + i,
-               com_lsatt_callback, afs_cb_result_handler, cc);
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LSATT);
+       return send_lls_callback_request(com_lsatt_callback, cmd, lpr, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(lsatt);
 
 struct addatt_event_data {
        const char *name;
        unsigned char bitnum;
 };
 
-
 static int com_addatt_callback(struct afs_callback_arg *aca)
 {
-       char *p;
-       int ret = 1;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADDATT);
+       int i, ret = 1;
        size_t len;
+       unsigned num_inputs;
 
-       for (
-               p = aca->query.data;
-               p < (char *)aca->query.data + aca->query.size;
-               p += len + 1
-       ) {
+       ret = lls(lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr));
+       assert(ret >= 0);
+       num_inputs = lls_num_inputs(aca->lpr);
+       for (i = 0; i < num_inputs; i++) {
+               const char *name = lls_input(i, aca->lpr);
                struct osl_object objs[NUM_ATT_COLUMNS];
                struct osl_row *row;
                unsigned char bitnum;
                struct addatt_event_data aed;
 
-               len = strlen(p);
-               if (!len || p[len - 1] == '-' || p[len - 1] == '+') {
-                       para_printf(&aca->pbout, "invalid attribute name: %s\n", p);
+               len = strlen(name);
+               if (len == 0 || name[len - 1] == '-' || name[len - 1] == '+') {
+                       para_printf(&aca->pbout,
+                               "invalid attribute name: %s\n", name);
                        continue;
                }
-               ret = get_attribute_bitnum_by_name(p, &bitnum);
+               ret = get_attribute_bitnum_by_name(name, &bitnum);
                if (ret >= 0) {
-                       para_printf(&aca->pbout, "attribute \"%s\" already exists\n", p);
+                       para_printf(&aca->pbout,
+                               "attribute \"%s\" already exists\n", name);
                        continue;
                }
                if (ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) /* error */
@@ -251,12 +220,12 @@ static int com_addatt_callback(struct afs_callback_arg *aca)
                        ret = -E_ATT_TABLE_FULL;
                        goto out;
                }
-               objs[ATTCOL_NAME].data = p;
+               objs[ATTCOL_NAME].data = (char *)name;
                objs[ATTCOL_NAME].size = len + 1;
                ret = osl(osl_add_row(attribute_table, objs));
                if (ret < 0)
                        goto out;
-               aed.name = p;
+               aed.name = name;
                aed.bitnum = bitnum;
                ret = afs_event(ATTRIBUTE_ADD, &aca->pbout, &aed);
                if (ret < 0)
@@ -265,53 +234,67 @@ static int com_addatt_callback(struct afs_callback_arg *aca)
        }
 out:
        if (ret < 0)
-               para_printf(&aca->pbout, "%s: %s\n", p, para_strerror(-ret));
+               para_printf(&aca->pbout, "error while adding %s\n",
+                       lls_input(i, aca->lpr));
        return ret;
 }
 
-int com_addatt(struct command_context *cc)
+static int com_addatt(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       int ret;
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADDATT);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 1, 64, &errctx));
 
-       if (cc->argc < 2)
-               return -E_ATTR_SYNTAX;
-       ret = send_standard_callback_request(cc->argc - 1, cc->argv + 1,
-               com_addatt_callback, afs_cb_result_handler, cc);
-       if (ret < 0)
-               send_strerror(cc, -ret);
-       return ret;
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       return send_lls_callback_request(com_addatt_callback, cmd, lpr, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(addatt);
 
 static int com_mvatt_callback(struct afs_callback_arg *aca)
 {
-       char *old = aca->query.data;
-       size_t size = strlen(old) + 1;
-       char *new = old + size;
-       struct osl_object obj = {.data = old, .size = size};
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(MVATT);
+       const char *old, *new;
+       struct osl_object obj;
        struct osl_row *row;
        int ret;
 
+       ret = lls(lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr));
+       assert(ret >= 0);
+       old = lls_input(0, aca->lpr);
+       new = lls_input(1, aca->lpr);
+       obj.data = (char *)old;
+       obj.size = strlen(old) + 1;
        ret = osl(osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row));
        if (ret < 0)
                goto out;
-       obj.data = new;
+       obj.data = (char *)new;
        obj.size = strlen(new) + 1;
+       /* The update fails if the destination attribute exists. */
        ret = osl(osl_update_object(attribute_table, row, ATTCOL_NAME, &obj));
 out:
        if (ret < 0)
                para_printf(&aca->pbout, "cannot rename %s to %s\n", old, new);
        else
                ret = afs_event(ATTRIBUTE_RENAME, &aca->pbout, NULL);
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-int com_mvatt(struct command_context *cc)
+static int com_mvatt(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       if (cc->argc != 3)
-               return -E_ATTR_SYNTAX;
-       return send_standard_callback_request(cc->argc - 1, cc->argv + 1,
-               com_mvatt_callback, afs_cb_result_handler, cc);
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(MVATT);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 2, 2, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       return send_lls_callback_request(com_mvatt_callback, cmd, lpr, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(mvatt);
 
 static int remove_attribute(struct osl_table *table, struct osl_row *row,
                const char *name, void *data)
@@ -336,31 +319,41 @@ static int remove_attribute(struct osl_table *table, struct osl_row *row,
 
 static int com_rmatt_callback(struct afs_callback_arg *aca)
 {
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RMATT);
        int ret;
        struct pattern_match_data pmd = {
                .table = attribute_table,
-               .patterns = aca->query,
                .loop_col_num = ATTCOL_BITNUM,
                .match_col_num = ATTCOL_NAME,
                .data = aca,
                .action = remove_attribute
        };
+       ret = lls(lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr));
+       assert(ret >= 0);
+       pmd.lpr = aca->lpr;
        ret = for_each_matching_row(&pmd);
        if (ret < 0)
                goto out;
        if (pmd.num_matches == 0)
                ret = -E_NO_MATCH;
 out:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-int com_rmatt(struct command_context *cc)
+static int com_rmatt(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       if (cc->argc < 2)
-               return -E_ATTR_SYNTAX;
-       return send_standard_callback_request(cc->argc - 1, cc->argv + 1,
-               com_rmatt_callback, afs_cb_result_handler, cc);
+       const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RMATT);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx));
+
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       return send_lls_callback_request(com_rmatt_callback, cmd, lpr, cc);
 }
+EXPORT_SERVER_CMD_HANDLER(rmatt);
 
 /**
  * Return a binary representation of the given attribute value.
@@ -479,7 +472,7 @@ int attribute_check_callback(struct afs_callback_arg *aca)
 /**
  * Close the attribute table.
  *
- * \sa osl_close_table().
+ * \sa \ref osl_close_table().
  */
 static void attribute_close(void)
 {
@@ -494,7 +487,7 @@ static void attribute_close(void)
  *
  * \return Positive on success, negative on errors.
  *
- * \sa osl_open_table().
+ * \sa \ref osl_open_table().
  */
 static int attribute_open(const char *dir)
 {
index f8fd80faa77bdea4a4f1139bb8168268ce59ddc3..af67063367044b675877173a1ed351bdd28f2dd1 100644 (file)
--- a/audioc.c
+++ b/audioc.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file audioc.c The client program used to connect to para_audiod. */
 
 #include <sys/un.h>
 #include <netdb.h>
 #include <signal.h>
+#include <lopsub.h>
+
+#include "audiod_cmd.lsg.h"
+#include "audioc.lsg.h"
 
-#include "audioc.cmdline.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "net.h"
 #include "string.h"
 #include "fd.h"
-#include "ggo.h"
 #include "version.h"
 
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
-/** The gengetopt structure containing command line args. */
-static struct audioc_args_info conf;
 static char *socket_name;
+static struct lls_parse_result *lpr;
 
+#define CMD_PTR (lls_cmd(0, audioc_suite))
+#define OPT_RESULT(_name) \
+       (lls_opt_result(LSG_AUDIOC_PARA_AUDIOC_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
 
 static int loglevel;
 INIT_STDERR_LOGGING(loglevel);
@@ -41,7 +45,8 @@ static char *concat_args(unsigned argc, char * const *argv)
        char *buf = NULL;
 
        for (i = 0; i < argc; i++) {
-               buf = para_strcat(buf, argv[i]);
+               const char *arg = argv? argv[i] : lls_input(i, lpr);
+               buf = para_strcat(buf, arg);
                if (i != argc - 1)
                        buf = para_strcat(buf, "\n");
        }
@@ -72,7 +77,6 @@ fail:
 #include "sched.h"
 #include "buffer_tree.h"
 #include "interactive.h"
-#include "audiod.completion.h"
 
 static struct sched sched;
 
@@ -92,15 +96,21 @@ I9E_DUMMY_COMPLETER(tasks);
 I9E_DUMMY_COMPLETER(term);
 
 static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
 {
-       result->matches = i9e_complete_commands(ci->word, audiod_completers);
+       char *opts[] = {LSG_AUDIOD_CMD_HELP_OPTS, NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, audiod_completers);
 }
 
 static void version_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-v", NULL};
+       char *opts[] = {LSG_AUDIOD_CMD_VERSION_OPTS, NULL};
 
        if (ci->word_num <= 2 && ci->word && ci->word[0] == '-')
                i9e_complete_option(opts, ci, cr);
@@ -109,8 +119,8 @@ static void version_completer(struct i9e_completion_info *ci,
 static void stat_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *sia[] = {STATUS_ITEM_ARRAY NULL};
-       char *opts[] = {"-p", NULL};
+       char *sia[] = {STATUS_ITEMS NULL};
+       char *opts[] = {LSG_AUDIOD_CMD_STAT_OPTS, NULL};
 
        if (ci->word_num <= 2 && ci->word && ci->word[0] == '-')
                i9e_complete_option(opts, ci, cr);
@@ -121,12 +131,15 @@ static void stat_completer(struct i9e_completion_info *ci,
 static void grab_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-ms", "-ms", "-ma", "-p=", "-n=", "-o", NULL};
+       char *opts[] = {LSG_AUDIOD_CMD_GRAB_OPTS, NULL};
        i9e_complete_option(opts, ci, cr);
 }
 
 static struct i9e_completer audiod_completers[] = {
-       AUDIOD_COMPLETERS
+#define LSG_AUDIOD_CMD_CMD(_name) {.name = #_name, \
+       .completer = _name ## _completer}
+       LSG_AUDIOD_CMD_SUBCOMMANDS
+#undef LSG_AUDIOD_CMD_CMD
        {.name = NULL}
 };
 
@@ -145,13 +158,15 @@ static int audioc_post_select(struct sched *s, void *context)
        char *buf = NULL;
        struct audioc_task *at = context;
        int ret = btr_node_status(at->btrn, 0, BTR_NT_ROOT);
+       size_t bufsize;
 
        if (ret < 0)
                goto out;
        if (!FD_ISSET(at->fd, &s->rfds))
                return 0;
-       buf = para_malloc(conf.bufsize_arg);
-       ret = recv_bin_buffer(at->fd, buf, conf.bufsize_arg);
+       bufsize = PARA_MAX(1024U, OPT_UINT32_VAL(BUFSIZE));
+       buf = para_malloc(bufsize);
+       ret = recv_bin_buffer(at->fd, buf, bufsize);
        PARA_DEBUG_LOG("recv: %d\n", ret);
        if (ret == 0)
                ret = -E_AUDIOC_EOF;
@@ -172,28 +187,26 @@ static struct audioc_task audioc_task, *at = &audioc_task;
 
 static int audioc_i9e_line_handler(char *line)
 {
-       char *args = NULL;
-       int ret;
+       int argc, ret;
+       char *args, **argv;
 
        PARA_DEBUG_LOG("line: %s\n", line);
-       ret = create_argv(line, " ", &conf.inputs);
+       ret = create_argv(line, " ", &argv);
        if (ret < 0)
                return ret;
-       conf.inputs_num = ret;
-       args = concat_args(conf.inputs_num, conf.inputs);
-       free_argv(conf.inputs);
+       argc = ret;
+       args = concat_args(argc, argv);
+       free_argv(argv);
        if (!args)
                return 0;
-       conf.inputs_num = 0; /* required for audioc_cmdline_parser_free() */
        ret = connect_audiod(socket_name, args);
+       free(args);
        if (ret < 0)
-               goto out;
+               return ret;
        at->fd = ret;
        ret = mark_fd_nonblocking(at->fd);
        if (ret < 0)
                goto close;
-       free(args);
-       args = NULL;
        at->btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "audioc line handler"));
        at->task = task_register(&(struct task_info) {
@@ -206,8 +219,6 @@ static int audioc_i9e_line_handler(char *line)
        return 1;
 close:
        close(at->fd);
-out:
-       free(args);
        return ret;
 }
 
@@ -223,9 +234,10 @@ __noreturn static void interactive_session(void)
                .loglevel = loglevel,
                .completers = audiod_completers,
        };
+
        PARA_NOTICE_LOG("\n%s\n", version_text("audioc"));
-       if (conf.history_file_given)
-               history_file = para_strdup(conf.history_file_arg);
+       if (OPT_GIVEN(HISTORY_FILE))
+               history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE));
        else {
                char *home = para_homedir();
                history_file = make_message("%s/.paraslash/audioc.history",
@@ -251,7 +263,7 @@ __noreturn static void interactive_session(void)
        para_log = stderr_log;
 out:
        free(history_file);
-       audioc_cmdline_parser_free(&conf);
+       free(socket_name);
        if (ret < 0)
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
        exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
@@ -279,27 +291,19 @@ __noreturn static void print_completions(void)
 
 #endif /* HAVE_READLINE */
 
-static char *configfile_exists(void)
+static void handle_help_flag(void)
 {
-       char *config_file;
-       struct stat statbuf;
-       char *home = para_homedir();
-
-       config_file = make_message("%s/.paraslash/audioc.conf", home);
-       free(home);
-       if (!stat(config_file, &statbuf))
-               return config_file;
-       free(config_file);
-       return NULL;
-}
-
-__noreturn static void print_help_and_die(void)
-{
-       struct ggo_help h = DEFINE_GGO_HELP(audioc);
-       bool d = conf.detailed_help_given;
+       char *help;
 
-       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
-       exit(0);
+       if (OPT_GIVEN(DETAILED_HELP))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       exit(EXIT_SUCCESS);
 }
 
 /**
@@ -316,58 +320,50 @@ __noreturn static void print_help_and_die(void)
  *
  * \return EXIT_SUCCESS or EXIT_FAILURE.
  *
- * \sa send_cred_buffer(), para_audioc(1), para_audiod(1).
+ * \sa \ref send_cred_buffer(), para_audioc(1), para_audiod(1).
  */
 int main(int argc, char *argv[])
 {
        int ret, fd;
-       char *cf, *buf = NULL, *args = NULL;
+       char *buf, *args, *errctx = NULL;
        size_t bufsize;
+       unsigned num_inputs;
 
-       audioc_cmdline_parser(argc, argv, &conf);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       version_handle_flag("audioc", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-       cf = configfile_exists();
-       if (cf) {
-               struct audioc_cmdline_parser_params params = {
-                       .override = 0,
-                       .initialize = 0,
-                       .check_required = 0,
-                       .check_ambiguity = 0,
-                       .print_errors = 1,
-
-               };
-               audioc_cmdline_parser_config_file(cf, &conf, &params);
-               free(cf);
-               loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       }
-       if (conf.socket_given)
-               socket_name = para_strdup(conf.socket_arg);
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+       if (ret < 0)
+               goto fail;
+       version_handle_flag("audioc", OPT_GIVEN(VERSION));
+       handle_help_flag();
+       ret = lsu_merge_config_file_options(NULL, "audioc.conf",
+               &lpr, CMD_PTR, audioc_suite, 0 /* default flags */);
+       if (ret < 0)
+               goto fail;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       if (OPT_GIVEN(COMPLETE))
+               print_completions();
+       if (OPT_GIVEN(SOCKET))
+               socket_name = para_strdup(OPT_STRING_VAL(SOCKET));
        else {
                char *hn = para_hostname();
                socket_name = make_message("/var/paraslash/audiod_socket.%s",
                        hn);
                free(hn);
        }
-
-       if (conf.complete_given)
-               print_completions();
-
-       if (conf.inputs_num == 0)
+       num_inputs = lls_num_inputs(lpr);
+       if (num_inputs == 0)
                interactive_session();
-       args = concat_args(conf.inputs_num, conf.inputs);
 
+       args = concat_args(num_inputs, NULL);
        ret = connect_audiod(socket_name, args);
        free(socket_name);
+       free(args);
        if (ret < 0)
                goto out;
        fd = ret;
        ret = mark_fd_blocking(STDOUT_FILENO);
        if (ret < 0)
                goto out;
-       bufsize = conf.bufsize_arg;
+       bufsize = PARA_MAX(1024U, OPT_UINT32_VAL(BUFSIZE));
        buf = para_malloc(bufsize);
        do {
                size_t n = ret = recv_bin_buffer(fd, buf, bufsize);
@@ -375,9 +371,13 @@ int main(int argc, char *argv[])
                        break;
                ret = write_all(STDOUT_FILENO, buf, n);
        } while (ret >= 0);
-out:
        free(buf);
-       free(args);
+out:
+       lls_free_parse_result(lpr, CMD_PTR);
+fail:
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", errctx);
+       free(errctx);
        if (ret < 0)
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
        return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
index 32b6895dcf0da9393538e649f7241878b89e96c0..88599c3fa297337b6a3f598b31f92d6e6b4b0993 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file audiod.c The paraslash's audio daemon. */
 
 #include <netdb.h>
 #include <signal.h>
 #include <pwd.h>
+#include <lopsub.h>
 
+#include "audiod.lsg.h"
+#include "recv_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "crypt.h"
-#include "audiod.cmdline.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "recv.h"
 #include "filter.h"
 #include "grab_client.h"
-#include "client.cmdline.h"
 #include "client.h"
 #include "audiod.h"
 #include "net.h"
 #include "string.h"
 #include "fd.h"
 #include "write.h"
-#include "write_common.h"
 #include "signal.h"
 #include "version.h"
 
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
+static struct lls_parse_result *lpr;
+#define CMD_PTR (lls_cmd(0, audiod_suite))
+#define OPT_RESULT(_name) (lls_opt_result(LSG_AUDIOD_PARA_AUDIOD_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
+#define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \
+       lls_opt(LSG_AUDIOD_PARA_AUDIOD_OPT_ ## _name, CMD_PTR)))
+
 __printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log;
 /** define the array containing all supported audio formats */
 const char *audio_formats[] = {AUDIOD_AUDIO_FORMAT_ARRAY NULL};
 
-DEFINE_RECEIVER_ARRAY;
-
 /** Defines how audiod handles one supported audio format. */
 struct audio_format_info {
-       /** pointer to the receiver for this audio format */
-       struct receiver *receiver;
-       /** the receiver configuration */
-       void *receiver_conf;
+       /** the receiver for this audio format */
+       int receiver_num;
+       /** Parsed receiver command line. */
+       struct lls_parse_result *receiver_lpr;
        /** the number of filters that should be activated for this audio format */
        unsigned int num_filters;
        /** Array of filter numbers to be activated. */
        unsigned *filter_nums;
        /** Pointer to the array of filter configurations. */
        void **filter_conf;
+       /** Parsed filter command line, one parse result per filter. */
+       struct lls_parse_result **filter_lpr;
        /** the number of filters that should be activated for this audio format */
        unsigned int num_writers;
-       /** Array of writer numbers to be activated. */
-       int *writer_nums;
-       /** pointer to the array of writer configurations */
-       void **writer_conf;
+       /** Array of writer IDs to be activated. */
+       int *wids;
+       /** Parsed writer command line(s) */
+       struct lls_parse_result **writer_lpr;
        /** do not start receiver/filters/writer before this time */
        struct timeval restart_barrier;
 };
@@ -88,6 +93,9 @@ struct slot_info {
        struct writer_node *wns;
 };
 
+#define RECEIVER_CMD(_a) lls_cmd((_a)->receiver_num, recv_cmd_suite)
+#define RECEIVER(_a) ((const struct receiver *)lls_user_data(RECEIVER_CMD(_a)))
+
 /** Maximal number of simultaneous instances. */
 #define MAX_STREAM_SLOTS 5
 
@@ -98,9 +106,7 @@ struct slot_info {
  * para_audiod uses \p MAX_STREAM_SLOTS different slots, each of which may
  * be associated with a receiver/filter/writer triple. This array holds all
  * information on the status of these slots.
- *
- * \sa struct slot_info
- * */
+ */
 struct slot_info slot[MAX_STREAM_SLOTS];
 
 /** The vss status flags audiod is interested in. */
@@ -156,54 +162,32 @@ struct status_task {
 char *stat_item_values[NUM_STAT_ITEMS] = {NULL};
 
 /**
- * the current mode of operation of which can be changed by the on/off/cycle
- * commands. It is either, AUDIOD_OFF, AUDIOD_ON or AUDIOD_STANDBY.
+ * The current mode of operation (AUDIOD_OFF, AUDIOD_ON or AUDIOD_STANDBY).
+ * Set by the on/off/cycle commands.
  */
 int audiod_status = AUDIOD_ON;
 
-/**
- * the gengetopt args_info struct that holds information on all command line
- * arguments
- */
-static struct audiod_args_info conf;
-
 static char *socket_name;
 static struct audio_format_info afi[NUM_AUDIO_FORMATS];
-
 static struct signal_task *signal_task;
-
 static struct status_task status_task_struct;
-
 static uid_t *uid_whitelist;
 
 /**
- * the task that calls the status command of para_server
+ * The task that calls the status command of para_server.
  *
- * \sa struct status_task
+ * \sa \ref struct status_task.
  */
 static struct status_task *stat_task = &status_task_struct;
 
-/*
- * The task for handling audiod commands.
- *
- * We need two listening sockets for backward compability: on Linux systems
- * fd[0] is an abstract socket (more precisely, a socket bound to an address in
- * the abstract namespace), and fd[1] is the usual pathname socket. On other
- * systems, fd[0] is negative, and only the pathname socket is used.
- *
- * For 0.5.x we accept connections on both sockets to make sure that old
- * para_audioc versions can still connect. New versions use only the abstract
- * socket. Hence after v0.6.0 we can go back to a single socket, either an
- * abstract one (Linux) or a pathname socket (all other systems).
- */
 struct command_task {
-       /** The local listening sockets. */
-       int fd[2];
-       /** the associated task structure */
+       /** The local listening socket. */
+       int fd;
+       /** The associated task structure. */
        struct task *task;
 };
 
-/** iterate over all supported audio formats */
+/** Iterate over all supported audio formats. */
 #define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++)
 
 /**
@@ -395,58 +379,37 @@ empty:
 
 static void parse_config_or_die(void)
 {
-       int ret, i;
-       char *config_file;
-       struct audiod_cmdline_parser_params params = {
-               .override = 0,
-               .initialize = 0,
-               .check_required = 1,
-               .check_ambiguity = 0,
-               .print_errors = 1
-       };
-
-       if (conf.config_file_given)
-               config_file = para_strdup(conf.config_file_arg);
-       else {
-               char *home = para_homedir();
-               config_file = make_message("%s/.paraslash/audiod.conf", home);
-               free(home);
-       }
-       ret = file_exists(config_file);
-       if (conf.config_file_given && !ret) {
-               PARA_EMERG_LOG("can not read config file %s\n", config_file);
-               free(config_file);
-               goto err;
-       }
-       if (ret) {
-               audiod_cmdline_parser_config_file(config_file, &conf, &params);
-               daemon_set_loglevel(conf.loglevel_arg);
+       int i, ret;
+       uint32_t n;
+
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "audiod.conf", &lpr, CMD_PTR, audiod_suite, 0U /* flags */);
+       if (ret < 0) {
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
-       free(config_file);
-       if (conf.user_allow_given > 0) {
-               uid_whitelist = para_malloc(conf.user_allow_given
-                       * sizeof(uid_t));
-               for (i = 0; i < conf.user_allow_given; i++) {
-                       int32_t val;
-                       struct passwd *pw;
-                       ret = para_atoi32(conf.user_allow_arg[i], &val);
-                       if (ret >= 0) {
-                               uid_whitelist[i] = val;
-                               continue;
-                       }
-                       errno = 0; /* see getpwnam(3) */
-                       pw = getpwnam(conf.user_allow_arg[i]);
-                       if (!pw) {
-                               PARA_EMERG_LOG("invalid username: %s\n",
-                                       conf.user_allow_arg[i]);
-                               goto err;
-                       }
-                       uid_whitelist[i] = pw->pw_uid;
+       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       n = OPT_GIVEN(USER_ALLOW);
+       if (n == 0)
+               return;
+       uid_whitelist = para_malloc(n * sizeof(uid_t));
+       for (i = 0; i < n; i++) {
+               const char *arg = lls_string_val(i, OPT_RESULT(USER_ALLOW));
+               int32_t val;
+               struct passwd *pw;
+               ret = para_atoi32(arg, &val);
+               if (ret >= 0) {
+                       uid_whitelist[i] = val;
+                       continue;
                }
+               pw = getpwnam(arg);
+               if (!pw) {
+                       PARA_EMERG_LOG("invalid username: %s\n", arg);
+                       exit(EXIT_FAILURE);
+               }
+               uid_whitelist[i] = pw->pw_uid;
        }
-       return;
-err:
-       exit(EXIT_FAILURE);
 }
 
 static void setup_signal_handling(void)
@@ -477,7 +440,7 @@ static void close_receiver(int slot_num)
        a = &afi[s->format];
        PARA_NOTICE_LOG("closing %s receiver in slot %d\n",
                audio_formats[s->format], slot_num);
-       a->receiver->close(s->receiver_node);
+       RECEIVER(a)->close(s->receiver_node);
        btr_remove_node(&s->receiver_node->btrn);
        task_reap(&s->receiver_node->task);
        free(s->receiver_node);
@@ -489,13 +452,10 @@ static void close_receiver(int slot_num)
 
 static void writer_cleanup(struct writer_node *wn)
 {
-       struct writer *w;
-
        if (!wn)
                return;
-       w = writers + wn->writer_num;
-       PARA_INFO_LOG("closing %s\n", writer_names[wn->writer_num]);
-       w->close(wn);
+       PARA_INFO_LOG("closing %s\n", writer_name(wn->wid));
+       writer_get(wn->wid)->close(wn);
        btr_remove_node(&wn->btrn);
        task_reap(&wn->task);
 }
@@ -590,17 +550,20 @@ static void open_filters(struct slot_info *s)
        parent = s->receiver_node->btrn;
        for (i = 0; i < nf; i++) {
                char buf[20];
+               const char *name;
                const struct filter *f = filter_get(a->filter_nums[i]);
                fn = s->fns + i;
                fn->filter_num = a->filter_nums[i];
                fn->conf = a->filter_conf[i];
+               fn->lpr = a->filter_lpr[i];
+               name = filter_name(fn->filter_num);
                fn->btrn = btr_new_node(&(struct btr_node_description)
-                       EMBRACE(.name = f->name, .parent = parent,
+                       EMBRACE(.name = name, .parent = parent,
                                .handler = f->execute, .context = fn));
 
                if (f->open)
                        f->open(fn);
-               sprintf(buf, "%s (slot %d)", f->name, (int)(s - slot));
+               sprintf(buf, "%s (slot %d)", name, (int)(s - slot));
                fn->task = task_register(&(struct task_info) {
                        .name = buf,
                        .pre_select = f->pre_select,
@@ -609,7 +572,7 @@ static void open_filters(struct slot_info *s)
                }, &sched);
                parent = fn->btrn;
                PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n",
-                       audio_formats[s->format], i,  nf, f->name, (int)(s - slot));
+                       audio_formats[s->format], i,  nf, name, (int)(s - slot));
        }
 }
 
@@ -625,11 +588,11 @@ static void open_writers(struct slot_info *s)
                * sizeof(struct writer_node));
        for (i = 0; i < a->num_writers; i++) {
                wn = s->wns + i;
-               wn->conf = a->writer_conf[i];
-               wn->writer_num = a->writer_nums[i];
+               wn->wid = a->wids[i];
+               wn->lpr = a->writer_lpr[i];
                register_writer_node(wn, parent, &sched);
                PARA_NOTICE_LOG("%s writer started in slot %d\n",
-                       writer_names[a->writer_nums[i]], (int)(s - slot));
+                       writer_name(a->wids[i]), (int)(s - slot));
        }
 }
 
@@ -639,7 +602,8 @@ static int open_receiver(int format)
        struct audio_format_info *a = &afi[format];
        struct slot_info *s;
        int ret, slot_num;
-       struct receiver *r = a->receiver;
+       const struct receiver *r = RECEIVER(a);
+       const char *name = lls_command_name(RECEIVER_CMD(a));
        struct receiver_node *rn;
 
        tv_add(now, &(struct timeval)EMBRACE(2, 0), &a->restart_barrier);
@@ -649,11 +613,12 @@ static int open_receiver(int format)
        slot_num = ret;
        rn = para_calloc(sizeof(*rn));
        rn->receiver = r;
-       rn->conf = a->receiver_conf;
+       rn->lpr = a->receiver_lpr;
        rn->btrn = btr_new_node(&(struct btr_node_description)
-               EMBRACE(.name = r->name, .context = rn));
+               EMBRACE(.name = name, .context = rn));
        ret = r->open(rn);
        if (ret < 0) {
+               PARA_ERROR_LOG("could not open %s receiver\n", name);
                btr_remove_node(&rn->btrn);
                free(rn);
                return ret;
@@ -662,9 +627,9 @@ static int open_receiver(int format)
        s->format = format;
        s->receiver_node = rn;
        PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n",
-               audio_formats[format], r->name, slot_num);
+               audio_formats[format], name, slot_num);
        rn->task = task_register(&(struct task_info) {
-               .name = r->name,
+               .name = name,
                .pre_select = r->pre_select,
                .post_select = r->post_select,
                .context = rn,
@@ -789,45 +754,45 @@ static int update_item(int itemnum, char *buf)
 {
        long unsigned sec, usec;
 
-       if (stat_task->clock_diff_count && itemnum != SI_CURRENT_TIME)
+       if (stat_task->clock_diff_count && itemnum != SI_current_time)
                return 1;
        free(stat_item_values[itemnum]);
        stat_item_values[itemnum] = para_strdup(buf);
        stat_client_write_item(itemnum);
        switch (itemnum) {
-       case SI_STATUS_FLAGS:
+       case SI_status_flags:
                stat_task->vss_status = 0;
                if (strchr(buf, 'N'))
                        stat_task->vss_status |= VSS_STATUS_FLAG_NEXT;
                if (strchr(buf, 'P'))
                        stat_task->vss_status |= VSS_STATUS_FLAG_PLAYING;
                break;
-       case SI_OFFSET:
+       case SI_offset:
                stat_task->offset_seconds = atoi(buf);
                break;
-       case SI_SECONDS_TOTAL:
+       case SI_seconds_total:
                stat_task->length_seconds = atoi(buf);
                break;
-       case SI_STREAM_START:
+       case SI_stream_start:
                if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) {
                        stat_task->server_stream_start.tv_sec = sec;
                        stat_task->server_stream_start.tv_usec = usec;
                }
                break;
-       case SI_CURRENT_TIME:
+       case SI_current_time:
                if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) {
                        struct timeval tv = {sec, usec};
                        compute_time_diff(&tv);
                }
                break;
-       case SI_FORMAT:
+       case SI_format:
                stat_task->current_audio_format_num
                        = get_audio_format_num(buf);
        }
        return 1;
 }
 
-static int parse_stream_command(const char *txt, char **cmd)
+static int parse_stream_command(const char *txt, const char **cmd)
 {
        int ret, len;
        char *re, *p = strchr(txt, ':');
@@ -836,7 +801,7 @@ static int parse_stream_command(const char *txt, char **cmd)
                return -E_MISSING_COLON;
        *cmd = p + 1;
        len = p - txt;
-       re = malloc(len + 1);
+       re = para_malloc(len + 1);
        strncpy(re, txt, len);
        re[len] = '\0';
        ret = get_matching_audio_format_nums(re);
@@ -844,38 +809,41 @@ static int parse_stream_command(const char *txt, char **cmd)
        return ret;
 }
 
-static int add_filter(int format, char *cmdline)
+static int add_filter(int format, const char *cmdline)
 {
        struct audio_format_info *a = &afi[format];
        int filter_num, nf = a->num_filters;
        void *cfg;
+       struct lls_parse_result *flpr;
 
-       filter_num = check_filter_arg(cmdline, &cfg);
-       if (filter_num < 0)
-               return filter_num;
+       filter_num = filter_setup(cmdline, &cfg, &flpr);
+       a->filter_lpr = para_realloc(a->filter_lpr,
+               (nf + 1) * sizeof(flpr));
        a->filter_conf = para_realloc(a->filter_conf,
                (nf + 1) * sizeof(void *));
        a->filter_nums = para_realloc(a->filter_nums,
                (nf + 1) * sizeof(unsigned));
+
        a->filter_nums[nf] = filter_num;
        a->filter_conf[nf] = cfg;
+       a->filter_lpr[nf] = flpr;
        a->num_filters++;
        PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf,
-               filter_get(filter_num)->name);
+               filter_name(filter_num));
        return filter_num;
 }
 
 static int parse_writer_args(void)
 {
        int i, ret;
-       char *cmd;
+       const char *cmd;
        struct audio_format_info *a;
 
-       for (i = 0; i < conf.writer_given; i++) {
-               void *wconf;
-               int j, nw, writer_num, af_mask;
+       for (i = 0; i < OPT_GIVEN(WRITER); i++) {
+               int j, nw, af_mask;
 
-               ret = parse_stream_command(conf.writer_arg[i], &cmd);
+               ret = parse_stream_command(lls_string_val(i,
+                       OPT_RESULT(WRITER)), &cmd);
                if (ret < 0)
                        return ret;
                af_mask = ret;
@@ -883,47 +851,45 @@ static int parse_writer_args(void)
                        a = afi + j;
                        if ((af_mask & (1 << j)) == 0) /* no match */
                                continue;
-                       wconf = check_writer_arg_or_die(cmd, &writer_num);
                        nw = a->num_writers;
-                       a->writer_nums = para_realloc(a->writer_nums, (nw + 1) * sizeof(int));
-                       a->writer_conf = para_realloc(a->writer_conf, (nw + 1) * sizeof(void *));
-                       a->writer_nums[nw] = writer_num;
-                       a->writer_conf[nw] = wconf;
+                       a->wids = para_realloc(a->wids, (nw + 1) * sizeof(int));
+                       a->writer_lpr = para_realloc(a->writer_lpr,
+                               (nw + 1) * sizeof(struct lls_parse_result *));
+                       a->wids[nw] = check_writer_arg_or_die(cmd,
+                               a->writer_lpr + nw);
                        PARA_INFO_LOG("%s writer #%d: %s\n", audio_formats[j],
-                               nw, writer_names[writer_num]);
+                               nw, writer_name(a->wids[nw]));
                        a->num_writers++;
                }
        }
        /* Use default writer for audio formats which are not yet set up. */
        FOR_EACH_AUDIO_FORMAT(i) {
-               void *writer_conf;
-               int writer_num;
                a = afi + i;
                if (a->num_writers > 0)
                        continue; /* already set up */
-               writer_conf = check_writer_arg_or_die(NULL, &writer_num);
-               a->writer_nums = para_malloc(sizeof(int));
-               a->writer_nums[0] = writer_num;
-               a->writer_conf = para_malloc(sizeof(void *));
-               a->writer_conf[0] = writer_conf;
                a->num_writers = 1;
+               a->wids = para_malloc(sizeof(int));
+               a->writer_lpr = para_malloc(sizeof(struct lls_parse_result *));
+               a->wids[0] = check_writer_arg_or_die(NULL, a->writer_lpr);
                PARA_INFO_LOG("%s writer: %s (default)\n", audio_formats[i],
-                       writer_names[writer_num]);
+                       writer_name(a->wids[0]));
        }
        return 1;
 }
 
 static int parse_receiver_args(void)
 {
-       int i, ret, receiver_num;
-       char *cmd = NULL;
+       int i, ret;
+       const char *arg;
        struct audio_format_info *a;
 
-       for (i = conf.receiver_given - 1; i >= 0; i--) {
-               char *arg;
+       FOR_EACH_AUDIO_FORMAT(i)
+               afi[i].receiver_num = -1;
+       for (i = OPT_GIVEN(RECEIVER) - 1; i >= 0; i--) {
                int j, af_mask;
 
-               ret = parse_stream_command(conf.receiver_arg[i], &arg);
+               ret = parse_stream_command(lls_string_val(i,
+                       OPT_RESULT(RECEIVER)), &arg);
                if (ret < 0)
                        goto out;
                af_mask = ret;
@@ -937,37 +903,27 @@ static int parse_receiver_args(void)
                         * config here. Since we are iterating backwards, the winning
                         * receiver arg is in fact the first one given.
                         */
-                       if (a->receiver_conf)
-                               a->receiver->free_config(a->receiver_conf);
-                       a->receiver_conf = check_receiver_arg(arg, &receiver_num);
-                       ret = -E_RECV_SYNTAX;
-                       if (!a->receiver_conf)
-                               goto out;
-                       a->receiver = receivers + receiver_num;
+                       lls_free_parse_result(a->receiver_lpr, RECEIVER_CMD(a));
+                       a->receiver_num = check_receiver_arg(arg, &a->receiver_lpr);
                }
        }
        /*
-        * Use the first available receiver with no arguments for those audio
-        * formats for which no receiver was specified.
+        * Use the default receiver for those audio formats for which no
+        * receiver was specified.
         */
-       cmd = para_strdup(receivers[0].name);
        FOR_EACH_AUDIO_FORMAT(i) {
-               a = &afi[i];
-               if (a->receiver_conf)
+               a = afi + i;
+               if (a->receiver_num >= 0)
                        continue;
-               a->receiver_conf = check_receiver_arg(cmd, &receiver_num);
-               if (!a->receiver_conf)
-                       return -E_RECV_SYNTAX;
-               a->receiver = &receivers[receiver_num];
+               a->receiver_num = check_receiver_arg(NULL, &a->receiver_lpr);
        }
        FOR_EACH_AUDIO_FORMAT(i) {
                a = afi + i;
                PARA_INFO_LOG("receiving %s streams via %s receiver\n",
-                       audio_formats[i], a->receiver->name);
+                       audio_formats[i], lls_command_name(RECEIVER_CMD(a)));
        }
        ret = 1;
 out:
-       free(cmd);
        return ret;
 }
 
@@ -977,6 +933,7 @@ static int init_default_filters(void)
 
        FOR_EACH_AUDIO_FORMAT(i) {
                struct audio_format_info *a = &afi[i];
+               const char *name = lls_command_name(RECEIVER_CMD(a));
                char *tmp;
                int j;
 
@@ -986,8 +943,7 @@ static int init_default_filters(void)
                 * udp and dccp streams are fec-encoded, so add fecdec as the
                 * first filter.
                 */
-               if (strcmp(afi[i].receiver->name, "udp") == 0 ||
-                               strcmp(afi[i].receiver->name, "dccp") == 0) {
+               if (strcmp(name, "udp") == 0 || strcmp(name, "dccp") == 0) {
                        tmp = para_strdup("fecdec");
                        add_filter(i, tmp);
                        free(tmp);
@@ -996,20 +952,20 @@ static int init_default_filters(void)
                }
                /* add "dec" to audio format name */
                tmp = make_message("%sdec", audio_formats[i]);
-               for (j = 0; filter_get(j); j++)
-                       if (!strcmp(tmp, filter_get(j)->name))
+               for (j = 1; filter_get(j); j++)
+                       if (!strcmp(tmp, filter_name(j)))
                                break;
                free(tmp);
                ret = -E_UNSUPPORTED_FILTER;
                if (!filter_get(j))
                        goto out;
-               tmp = para_strdup(filter_get(j)->name);
+               tmp = para_strdup(filter_name(j));
                ret = add_filter(i, tmp);
                free(tmp);
                if (ret < 0)
                        goto out;
                PARA_INFO_LOG("%s -> default filter: %s\n", audio_formats[i],
-                       filter_get(j)->name);
+                       filter_name(j));
        }
 out:
        return ret;
@@ -1019,9 +975,10 @@ static int parse_filter_args(void)
 {
        int i, j, ret, af_mask, num_matches;
 
-       for (i = 0; i < conf.filter_given; i++) {
-               char *arg;
-               ret = parse_stream_command(conf.filter_arg[i], &arg);
+       for (i = 0; i < OPT_GIVEN(FILTER); i++) {
+               const char *arg;
+               ret = parse_stream_command(lls_string_val(i,
+                       OPT_RESULT(FILTER)), &arg);
                if (ret < 0)
                        goto out;
                af_mask = ret;
@@ -1036,7 +993,7 @@ static int parse_filter_args(void)
                }
                if (num_matches == 0)
                        PARA_WARNING_LOG("ignoring filter spec: %s\n",
-                               conf.filter_arg[i]);
+                               lls_string_val(i, OPT_RESULT(FILTER)));
        }
        ret = init_default_filters(); /* use default values for the rest */
 out:
@@ -1060,10 +1017,10 @@ static int parse_stream_args(void)
 }
 
 /* does not unlink socket on errors */
-static void init_local_sockets(struct command_task *ct)
+static void init_local_socket(struct command_task *ct)
 {
-       if (conf.socket_given)
-               socket_name = para_strdup(conf.socket_arg);
+       if (OPT_GIVEN(SOCKET))
+               socket_name = para_strdup(OPT_STRING_VAL(SOCKET));
        else {
                char *hn = para_hostname();
                socket_name = make_message("/var/paraslash/audiod_socket.%s",
@@ -1071,14 +1028,12 @@ static void init_local_sockets(struct command_task *ct)
                free(hn);
        }
        PARA_NOTICE_LOG("local socket: %s\n", socket_name);
-       if (conf.force_given)
+       if (OPT_GIVEN(FORCE))
                unlink(socket_name);
-       ct->fd[0] = create_local_socket(socket_name, 0);
-       ct->fd[1] = create_local_socket(socket_name,
-               S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
-       if (ct->fd[0] >= 0 || ct->fd[1] >= 0)
+       ct->fd = create_local_socket(socket_name);
+       if (ct->fd >= 0)
                return;
-       PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd[1]));
+       PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd));
        exit(EXIT_FAILURE);
 }
 
@@ -1105,16 +1060,12 @@ static int signal_post_select(struct sched *s, void *context)
 static void command_pre_select(struct sched *s, void *context)
 {
        struct command_task *ct = context;
-       int i;
-
-       for (i = 0; i < 2; i++)
-               if (ct->fd[i] >= 0)
-                       para_fd_set(ct->fd[i], &s->rfds, &s->max_fileno);
+       para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
 }
 
 static int command_post_select(struct sched *s, void *context)
 {
-       int ret, i;
+       int ret;
        struct command_task *ct = context;
        static struct timeval last_status_dump;
        struct timeval tmp, delay;
@@ -1123,19 +1074,15 @@ static int command_post_select(struct sched *s, void *context)
        ret = task_get_notification(ct->task);
        if (ret < 0)
                return ret;
-       for (i = 0; i < 2; i++) {
-               if (ct->fd[i] < 0)
-                       continue;
-               ret = handle_connect(ct->fd[i], &s->rfds);
-               if (ret < 0) {
-                       PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-                       if (ret == -E_AUDIOD_TERM) {
-                               task_notify_all(s, -ret);
-                               return ret;
-                       }
-               } else if (ret > 0)
-                       force = true;
-       }
+       ret = handle_connect(ct->fd, &s->rfds);
+       if (ret < 0) {
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               if (ret == -E_AUDIOD_TERM) {
+                       task_notify_all(s, -ret);
+                       return ret;
+               }
+       } else if (ret > 0)
+               force = true;
        if (force == true)
                goto dump;
 
@@ -1163,7 +1110,7 @@ dump:
 
 static void init_command_task(struct command_task *ct)
 {
-       init_local_sockets(ct); /* doesn't return on errors */
+       init_local_socket(ct); /* doesn't return on errors */
 
        ct->task = task_register(&(struct task_info) {
                .name = "command",
@@ -1256,7 +1203,6 @@ static void audiod_cleanup(void)
                unlink(socket_name);
        close_stat_pipe();
        close_unused_slots();
-       audiod_cmdline_parser_free(&conf);
        close_stat_clients();
        free(uid_whitelist);
 }
@@ -1337,7 +1283,7 @@ static int status_post_select(struct sched *s, void *context)
                        goto out;
                }
                close_stat_pipe();
-               st->clock_diff_count = conf.clock_diff_count_arg;
+               st->clock_diff_count = OPT_UINT32_VAL(CLOCK_DIFF_COUNT);
                goto out;
        }
        if (st->ct) {
@@ -1391,10 +1337,10 @@ static int status_post_select(struct sched *s, void *context)
                client_open(argc, argv, &st->ct, NULL, NULL, st->btrn, s);
                set_stat_task_restart_barrier(5);
        }
-       free(stat_item_values[SI_BASENAME]);
-       stat_item_values[SI_BASENAME] = para_strdup(
+       free(stat_item_values[SI_basename]);
+       stat_item_values[SI_basename] = para_strdup(
                "no connection to para_server");
-       stat_client_write_item(SI_BASENAME);
+       stat_client_write_item(SI_basename);
        st->last_status_read = *now;
 out:
        start_stop_decoders();
@@ -1405,7 +1351,7 @@ static void init_status_task(struct status_task *st)
 {
        memset(st, 0, sizeof(struct status_task));
        st->sa_time_diff_sign = 1;
-       st->clock_diff_count = conf.clock_diff_count_arg;
+       st->clock_diff_count = OPT_UINT32_VAL(CLOCK_DIFF_COUNT);
        st->current_audio_format_num = -1;
        st->btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "stat"));
@@ -1421,36 +1367,20 @@ static void init_status_task(struct status_task *st)
 static void set_initial_status(void)
 {
        audiod_status = AUDIOD_ON;
-       if (!conf.mode_given)
+       if (!OPT_GIVEN(MODE))
                return;
-       if (!strcmp(conf.mode_arg, "sb")) {
+       if (!strcmp(OPT_STRING_VAL(MODE), "sb")) {
                audiod_status = AUDIOD_STANDBY;
                return;
        }
-       if (!strcmp(conf.mode_arg, "off")) {
+       if (!strcmp(OPT_STRING_VAL(MODE), "off")) {
                audiod_status = AUDIOD_OFF;
                return;
        }
-       if (strcmp(conf.mode_arg, "on"))
+       if (strcmp(OPT_STRING_VAL(MODE), "on"))
                PARA_WARNING_LOG("invalid mode\n");
 }
 
-__noreturn static void print_help_and_die(void)
-{
-       struct ggo_help h = DEFINE_GGO_HELP(audiod);
-       bool d = conf.detailed_help_given;
-       unsigned flags;
-
-       flags = d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS;
-       ggo_print_help(&h, flags);
-
-       flags = d? GPH_MODULE_FLAGS_DETAILED : GPH_MODULE_FLAGS;
-       print_receiver_helps(flags);
-       print_filter_helps(flags);
-       print_writer_helps(flags);
-       exit(0);
-}
-
 /**
  * Lookup the given UID in the whitelist.
  *
@@ -1466,14 +1396,33 @@ bool uid_is_whitelisted(uid_t uid)
 {
        int i;
 
-       if (!conf.user_allow_given)
+       if (!OPT_GIVEN(USER_ALLOW))
                return true;
-       for (i = 0; i < conf.user_allow_given; i++)
+       for (i = 0; i < OPT_GIVEN(USER_ALLOW); i++)
                if (uid == uid_whitelist[i])
                        return true;
        return false;
 }
 
+static void handle_help_flags(void)
+{
+       char *help;
+       bool d = OPT_GIVEN(DETAILED_HELP);
+
+       if (d)
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       print_receiver_helps(d);
+       print_filter_helps(d);
+       print_writer_helps(d);
+       exit(EXIT_SUCCESS);
+}
+
 /**
  * the main function of para_audiod
  *
@@ -1488,40 +1437,33 @@ int main(int argc, char *argv[])
 {
        int ret, i;
        struct command_task command_task_struct, *cmd_task = &command_task_struct;
-       struct audiod_cmdline_parser_params params = {
-               .override = 0,
-               .initialize = 1,
-               .check_required = 0,
-               .check_ambiguity = 0,
-               .print_errors = 1
-       };
+       char *errctx;
 
        valid_fd_012();
-       audiod_cmdline_parser_ext(argc, argv, &conf, &params);
-       daemon_set_loglevel(conf.loglevel_arg);
-       version_handle_flag("audiod", conf.version_given);
-       /* init receivers/filters/writers early to make help work */
-       recv_init();
-       filter_init();
-       writer_init();
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-       daemon_set_priority(conf.priority_arg);
-       daemon_drop_privileges_or_die(conf.user_arg, conf.group_arg);
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_drop_privileges_or_die(OPT_STRING_VAL(USER),
+               OPT_STRING_VAL(GROUP));
+       version_handle_flag("audiod", OPT_GIVEN(VERSION));
+       handle_help_flags();
        parse_config_or_die();
-       if (daemon_init_colors_or_die(conf.color_arg, color_arg_auto, color_arg_no,
-               conf.logfile_given)) {
-                       for (i = 0; i < conf.log_color_given; i++)
-                               daemon_set_log_color_or_die(conf.log_color_arg[i]);
+       crypt_init();
+       daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
+       if (daemon_init_colors_or_die(OPT_UINT32_VAL(COLOR), COLOR_AUTO,
+                       COLOR_NO, OPT_GIVEN(LOGFILE))) {
+               for (i = 0; i < OPT_GIVEN(LOG_COLOR); i++)
+                       daemon_set_log_color_or_die(lls_string_val(i,
+                               OPT_RESULT(LOG_COLOR)));
        }
-       init_random_seed_or_die();
        daemon_set_flag(DF_LOG_TIME);
        daemon_set_flag(DF_LOG_HOSTNAME);
        daemon_set_flag(DF_LOG_LL);
-       if (conf.log_timing_given)
+       if (OPT_GIVEN(LOG_TIMING))
                daemon_set_flag(DF_LOG_TIMING);
-       if (conf.logfile_given) {
-               daemon_set_logfile(conf.logfile_arg);
+       if (OPT_GIVEN(LOGFILE)) {
+               daemon_set_logfile(OPT_STRING_VAL(LOGFILE));
                daemon_open_log_or_die();
        }
        ret = parse_stream_args();
@@ -1539,7 +1481,7 @@ int main(int argc, char *argv[])
        init_status_task(stat_task);
        init_command_task(cmd_task);
 
-       if (conf.daemon_given)
+       if (OPT_GIVEN(DAEMON))
                daemonize(false /* parent exits immediately */);
 
        signal_task->task = task_register(&(struct task_info) {
@@ -1555,7 +1497,11 @@ int main(int argc, char *argv[])
        audiod_cleanup();
        sched_shutdown(&sched);
        signal_shutdown(signal_task);
-
+       crypt_shutdown();
+out:
+       lls_free_parse_result(lpr, CMD_PTR);
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", errctx);
        if (ret < 0)
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
diff --git a/audiod.cmd b/audiod.cmd
deleted file mode 100644 (file)
index 18c802d..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-BN: audiod
-SF: audiod_command.c
-SN: list of audiod commands
----
-N: cycle
-D: switch to next mode
-U: cycle
-H: on -> standby -> off -> on
----
-N: grab
-D: grab the audio stream
-L:
-U: -- grab [-m[{s|p|a}]] [-p=PARENT] [-n=NAME] [-o]
-H:
-H: grab ('splice') the audio stream at any position in the buffer
-H: tree and send that data back to the client.
-H:
-H: Options:
-H:
-H: -m  Change grab mode. Defaults to sloppy grab if not given.
-H:
-H:    -ms: sloppy grab
-H:    -mp: pedantic grab
-H:    -ma: aggressive grab
-H:
-H: The various grab modes only differ in what happens if an attempt to
-H: write the grabbed audio data would block. Sloppy mode ignores the
-H: write, pedantic mode aborts and aggressive mode tries to write anyway.
-H:
-H: -p  Grab output of node PARENT of the buffer tree.
-H:
-H: -n  Name of the new buffer tree node. Defaults to 'grab'.
-H:
-H: -o  One-shot mode: Stop grabbing if audio file changes.
----
-N: help
-D: display command list or help for given command
-U: help [command]
-H: When I was younger, so much younger than today, I never needed anybody's help
-H: in any way. But now these days are gone, I'm not so self assured. Now I find
-H: I've changed my mind and opened up the doors.
-H:                                                              -- Beatles: Help
----
-N: off
-D: deactivate para_audiod
-U: off
-H: Close connection to para_server and stop all decoders.
----
-N: on
-D: activate para_audiod
-U: on
-H: Establish connection to para_server, retrieve para_server's current status. If
-H: playing, start corresponding decoder. Otherwise stop all decoders.
----
-N: sb
-D: enter standby mode
-U: sb
-H: Stop all decoders but leave connection to para_server open.
----
-N: stat
-D: print status information
-U: stat [-p] [item1 ...]
-H: Dump given status items (all if none given) to stdout. If -p is given, use
-H: parser-friendly mode.
----
-N: tasks
-D: list current tasks
-U: tasks
-H: Print the list of task ids together with the status of each task.
----
-N: term
-D: terminate audiod
-U: term
-H: Stop all decoders, shut down connection to para_server and exit.
----
-N: version
-D: print the version of para_audiod
-U: version [-v]
-H: If the -v option is given, a more detailed version text is printed.
index 7073c6dd2108fb31b915724fdbed431d76d51e63..b40fdd6743faa4650888c2bdf69756d6c12e40d8 100644 (file)
--- a/audiod.h
+++ b/audiod.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file audiod.h Symbols exported from audiod.c. */
 
@@ -13,15 +9,6 @@ enum {AUDIOD_AUDIO_FORMATS_ENUM};
 /** array of audio format names supported by para_audiod */
 extern const char *audio_formats[];
 
-/**
- * the possible modes of operation
- *
- * - off: disconnect from para_server
- * - on: receive status information from para_server and play the audio stream
- * - sb: only receive status information but not the audio stream
- */
-enum audiod_status_info {AUDIOD_OFF, AUDIOD_ON, AUDIOD_STANDBY};
-
 extern int audiod_status;
 
 /* defined in audiod.c */
index 3a39027523a72c686d997d1b358912dc5dfd2363..bb54dfab87f7965a18f6cccbe6bc4077c147fa29 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file audiod_command.c Commands for para_audiod. */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "audiod.lsg.h"
 #include "para.h"
-#include "audiod.cmdline.h"
-#include "audiod.command_list.h"
+#include "lsu.h"
+#include "audiod_cmd.lsg.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "grab_client.h"
 extern struct sched sched;
 extern char *stat_item_values[NUM_STAT_ITEMS];
 
-typedef int audiod_command_handler_t(int, int, char **);
-static audiod_command_handler_t AUDIOD_COMMAND_HANDLERS;
-
-/* Defines one command of para_audiod. */
-struct audiod_command {
-       const char *name;
-       /* Pointer to the function that handles the command. */
-       /*
-        * Command handlers must never never close their file descriptor. A
-        * positive return value tells audiod that the status items have
-        * changed. In this case audiod will send an updated version of all
-        * status items to to each connected stat client.
-        */
-       audiod_command_handler_t *handler;
-       /* One-line description. */
-       const char *description;
-       /* Summary of the command line options. */
-       const char *usage;
-       /* The long help text. */
-       const char *help;
-};
+/** The maximal number of simultaneous connections. */
+#define MAX_STAT_CLIENTS 50
 
-static struct audiod_command audiod_cmds[] = {DEFINE_AUDIOD_CMD_ARRAY};
+/** Pointer to a command handler function. */
+typedef int (*audiod_cmd_handler_t)(int, struct lls_parse_result *);
 
-/** Iterate over the array of all audiod commands. */
-#define FOR_EACH_COMMAND(c) for (c = 0; audiod_cmds[c].name; c++)
+/** The lopsub user_data pointer. Only the command handler at the moment. */
+struct audiod_command_info {
+       audiod_cmd_handler_t handler; /**< Implementation of the command. */
+};
 
-/** The maximal number of simultaneous connections. */
-#define MAX_STAT_CLIENTS 50
+/** Define the user_data pointer as expected by lopsub. */
+#define EXPORT_AUDIOD_CMD_HANDLER(_cmd) \
+       /** Implementation of _cmd. */ \
+       const struct audiod_command_info lsg_audiod_cmd_com_ ## _cmd ## _user_data = { \
+               .handler = com_ ## _cmd \
+       };
 
 /** Flags used for the stat command of para_audiod. */
 enum stat_client_flags {
@@ -94,7 +79,7 @@ static INITIALIZED_LIST_HEAD(client_list);
 static int num_clients;
 
 /** The list of all status items used by para_{server,audiod,gui}. */
-const char *status_item_list[] = {STATUS_ITEM_ARRAY};
+const char *status_item_list[] = {STATUS_ITEMS};
 
 static void dump_stat_client_list(void)
 {
@@ -240,93 +225,56 @@ __malloc static char *audiod_status_string(void)
        return para_strdup(status);
 }
 
-static int dump_commands(int fd)
+static int com_help(int fd, struct lls_parse_result *lpr)
 {
-       char *buf = para_strdup(""), *tmp = NULL;
-       int i;
-       ssize_t ret;
+       char *buf;
+       int ret;
+       const struct lls_opt_result *r =
+               lls_opt_result(LSG_AUDIOD_CMD_HELP_OPT_LONG, lpr);
+       bool long_help = lls_opt_given(r);
 
-       FOR_EACH_COMMAND(i) {
-               tmp = make_message("%s%s\t%s\n", buf, audiod_cmds[i].name,
-                       audiod_cmds[i].description);
-               free(buf);
-               buf = tmp;
-       }
+       lsu_com_help(long_help, lpr, audiod_cmd_suite, NULL, &buf, NULL);
        ret = client_write(fd, buf);
        free(buf);
-       return ret;
-}
-
-static int com_help(int fd, int argc, char **argv)
-{
-       int i, ret;
-       char *buf;
-
-       if (argc < 2) {
-               ret = dump_commands(fd);
-               goto out;
-       }
-       FOR_EACH_COMMAND(i) {
-               if (strcmp(audiod_cmds[i].name, argv[1]))
-                       continue;
-               buf = make_message(
-                       "NAME\n\t%s -- %s\n"
-                       "SYNOPSIS\n\tpara_audioc %s\n"
-                       "DESCRIPTION\n%s\n",
-                       argv[1],
-                       audiod_cmds[i].description,
-                       audiod_cmds[i].usage,
-                       audiod_cmds[i].help
-               );
-               ret = client_write(fd, buf);
-               free(buf);
-               goto out;
-       }
-       ret = client_write(fd, "No such command. Available commands:\n");
-       if (ret > 0)
-               ret = dump_commands(fd);
-out:
        return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(help)
 
-static int com_tasks(int fd, __a_unused int argc, __a_unused char **argv)
+static int com_tasks(int fd, __a_unused struct lls_parse_result *lpr)
 {
+       int ret;
        char *tl = get_task_list(&sched);
-       int ret = 1;
 
-       if (tl)
-               ret = client_write(fd, tl);
+       if (!tl) /* no tasks registered yet */
+               return 0;
+       ret = client_write(fd, tl);
        free(tl);
-       return ret < 0? ret : 0;
+       return ret;
 }
+EXPORT_AUDIOD_CMD_HANDLER(tasks)
 
-static int com_stat(int fd, int argc, char **argv)
+static int com_stat(int fd, struct lls_parse_result *lpr)
 {
        int i, ret, parser_friendly = 0;
        uint64_t mask = 0;
        const uint64_t one = 1;
        struct para_buffer b = {.flags = 0};
+       const struct lls_opt_result *r;
+       unsigned num_inputs;
 
        ret = mark_fd_nonblocking(fd);
        if (ret < 0)
                return ret;
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strncmp(arg, "-p", 2)) {
-                       parser_friendly = 1;
-                       b.flags = PBF_SIZE_PREFIX;
-               }
+       r = lls_opt_result(LSG_AUDIOD_CMD_STAT_OPT_PARSER_FRIENDLY, lpr);
+       if (lls_opt_given(r) > 0) {
+               parser_friendly = 1;
+               b.flags = PBF_SIZE_PREFIX;
        }
-       if (i >= argc)
+       num_inputs = lls_num_inputs(lpr);
+       if (num_inputs == 0)
                mask--; /* set all bits */
-       for (; i < argc; i++) {
-               ret = stat_item_valid(argv[i]);
+       for (i = 0; i < num_inputs; i++) {
+               ret = stat_item_valid(lls_input(i, lpr));
                if (ret < 0)
                        return ret;
                mask |= (one << ret);
@@ -344,58 +292,61 @@ static int com_stat(int fd, int argc, char **argv)
        free(b.buf);
        return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(stat)
 
-static int com_grab(int fd, int argc, char **argv)
+static int com_grab(int fd, struct lls_parse_result *lpr)
 {
-       int ret = grab_client_new(fd, argc, argv, &sched);
+       int ret = grab_client_new(fd, lpr, &sched);
        return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(grab)
 
-static int com_term(__a_unused int fd, __a_unused int argc, __a_unused char **argv)
+static int com_term(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        return -E_AUDIOD_TERM;
 }
+EXPORT_AUDIOD_CMD_HANDLER(term)
 
-static int com_on(__a_unused int fd, __a_unused int argc, __a_unused char **argv)
+static int com_on(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        audiod_status = AUDIOD_ON;
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(on)
 
-static int com_off(__a_unused int fd, __a_unused int argc, __a_unused char **argv)
+static int com_off(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        audiod_status = AUDIOD_OFF;
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(off)
 
-static int com_sb(__a_unused int fd, __a_unused int argc, __a_unused char **argv)
+static int com_sb(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        audiod_status = AUDIOD_STANDBY;
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(sb)
 
-static int com_cycle(__a_unused int fd, int argc, char **argv)
+static int com_cycle(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        switch (audiod_status) {
-               case  AUDIOD_ON:
-                       return com_sb(fd, argc, argv);
-                       break;
-               case  AUDIOD_OFF:
-                       return com_on(fd, argc, argv);
-                       break;
-               case  AUDIOD_STANDBY:
-                       return com_off(fd, argc, argv);
-                       break;
+               case  AUDIOD_ON: audiod_status = AUDIOD_STANDBY; break;
+               case  AUDIOD_OFF: audiod_status = AUDIOD_ON; break;
+               case  AUDIOD_STANDBY: audiod_status = AUDIOD_OFF; break;
        }
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(cycle)
 
-static int com_version(int fd, int argc, char **argv)
+static int com_version(int fd, struct lls_parse_result *lpr)
 {
        int ret;
        char *msg;
+       const struct lls_opt_result *r_v;
 
-       if (argc > 1 && strcmp(argv[1], "-v") == 0)
+       r_v = lls_opt_result(LSG_AUDIOD_CMD_VERSION_OPT_VERBOSE, lpr);
+       if (lls_opt_given(r_v))
                msg = make_message("%s", version_text("audiod"));
        else
                msg = make_message("%s\n", version_single_line("audiod"));
@@ -403,6 +354,7 @@ static int com_version(int fd, int argc, char **argv)
        free(msg);
        return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(version)
 
 /**
  * Handle arriving connections on the local socket.
@@ -419,14 +371,18 @@ static int com_version(int fd, int argc, char **argv)
  * \return Positive on success, negative on errors, zero if there was no
  * connection to accept.
  *
- * \sa para_accept(), recv_cred_buffer()
+ * \sa \ref para_accept(), \ref recv_cred_buffer().
  * */
 int handle_connect(int accept_fd, fd_set *rfds)
 {
-       int i, argc, ret, clifd;
+       int argc, ret, clifd;
        char buf[MAXLINE], **argv = NULL;
        struct sockaddr_un unix_addr;
        uid_t uid;
+       const struct lls_command *cmd;
+       struct lls_parse_result *lpr;
+       char *errctx = NULL;
+       const struct audiod_command_info *aci;
 
        ret = para_accept(accept_fd, rfds, &unix_addr, sizeof(struct sockaddr_un), &clifd);
        if (ret <= 0)
@@ -443,18 +399,27 @@ int handle_connect(int accept_fd, fd_set *rfds)
        if (ret <= 0)
                goto out;
        argc = ret;
-       //PARA_INFO_LOG("argv[0]: %s, argc = %d\n", argv[0], argc);
-       FOR_EACH_COMMAND(i) {
-               if (strcmp(audiod_cmds[i].name, argv[0]))
-                       continue;
-               ret = audiod_cmds[i].handler(clifd, argc, argv);
+       ret = lls(lls_lookup_subcmd(argv[0], audiod_cmd_suite, &errctx));
+       if (ret < 0)
                goto out;
-       }
-       ret = -E_INVALID_AUDIOD_CMD;
+       cmd = lls_cmd(ret, audiod_cmd_suite);
+       ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       aci = lls_user_data(cmd);
+       ret = aci->handler(clifd, lpr);
+       lls_free_parse_result(lpr, cmd);
 out:
        free_argv(argv);
        if (ret < 0 && ret != -E_CLIENT_WRITE) {
-               char *tmp = make_message("%s\n", para_strerror(-ret));
+               char *tmp;
+               if (errctx) {
+                       tmp = make_message("%s\n", errctx);
+                       free(errctx);
+                       client_write(clifd, tmp);
+                       free(tmp);
+               }
+               tmp = make_message("%s\n", para_strerror(-ret));
                client_write(clifd, tmp);
                free(tmp);
        }
@@ -471,41 +436,41 @@ void audiod_status_dump(bool force)
 {
        char *old, *new;
 
-       old = stat_item_values[SI_PLAY_TIME];
+       old = stat_item_values[SI_play_time];
        new = get_time_string();
        if (new) {
                if (force || !old || strcmp(old, new)) {
                        free(old);
-                       stat_item_values[SI_PLAY_TIME] = new;
-                       stat_client_write_item(SI_PLAY_TIME);
+                       stat_item_values[SI_play_time] = new;
+                       stat_client_write_item(SI_play_time);
                } else
                        free(new);
        }
 
        new = daemon_get_uptime_str(now);
-       old = stat_item_values[SI_AUDIOD_UPTIME];
+       old = stat_item_values[SI_audiod_uptime];
        if (force || !old || strcmp(old, new)) {
                free(old);
-               stat_item_values[SI_AUDIOD_UPTIME] = new;
-               stat_client_write_item(SI_AUDIOD_UPTIME);
+               stat_item_values[SI_audiod_uptime] = new;
+               stat_client_write_item(SI_audiod_uptime);
        } else
                free(new);
 
-       old = stat_item_values[SI_AUDIOD_STATUS];
+       old = stat_item_values[SI_audiod_status];
        new = audiod_status_string();
        if (force || !old || strcmp(old, new)) {
                free(old);
-               stat_item_values[SI_AUDIOD_STATUS] = new;
-               stat_client_write_item(SI_AUDIOD_STATUS);
+               stat_item_values[SI_audiod_status] = new;
+               stat_client_write_item(SI_audiod_status);
        } else
                free(new);
 
-       old = stat_item_values[SI_DECODER_FLAGS];
+       old = stat_item_values[SI_decoder_flags];
        new = audiod_get_decoder_flags();
        if (force || !old || strcmp(old, new)) {
                free(old);
-               stat_item_values[SI_DECODER_FLAGS] = new;
-               stat_client_write_item(SI_DECODER_FLAGS);
+               stat_item_values[SI_decoder_flags] = new;
+               stat_client_write_item(SI_decoder_flags);
        } else
                free(new);
 }
index d432adbb64aa08a74e4dab7bd21360ebb2ef4227..caf1401d8bf02e2d299bdcdf58ae902cfb260092 100755 (executable)
@@ -24,5 +24,5 @@ autoheader
 echo configuring...
 ./configure $@ > /dev/null
 echo compiling...
-make clean2 > /dev/null 2>&1
-make -j $n > /dev/null
+make clean > /dev/null 2>&1
+make -j $n > /dev/null && make check
index 4bfaa99d05eb6d3631d978f1fac2827823f8ec57..ea62d98fd92759eefae19492909bcc6a054fd097 100644 (file)
--- a/base64.h
+++ b/base64.h
@@ -1,3 +1,7 @@
+/* Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file base64.h uudecode/base64 API. */
+
 int uudecode(char const *src, size_t encoded_size, char **result,
                size_t *decoded_size);
 int base64_decode(char const *src, size_t encoded_size, char **result,
index 3dd25e5ae6d1630996ca005cf328cfefc846581a..88b37dc28fb0dc175f48ac9fa364f26a434bedde 100644 (file)
@@ -1,7 +1,4 @@
-# Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
-#
-# Licensed under the GPL v2. For licencing details see COPYING.
-
+# Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING.
 _para_complete()
 {
        local prg="$1" # the program to execute
index 638d19a3579b02053bd281e29e7f39d2bf54495c..dfc1e55e67aced611014fc90b86128e8bd61a457 100644 (file)
@@ -6,9 +6,9 @@
  * Copyright (c) 2000, 2001 Fabrice Bellard
  * Copyright (c) 2002-2004 Michael Niedermayer <michaelni@gmx.at>
  * alternative bitstream reader & writer by Michael Niedermayer <michaelni@gmx.at>
+ * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
  *
- * Licensed under the GNU Lesser General Public License.
- * For licencing details see COPYING.LIB.
+ * Licensed under the GNU Lesser General Public License. see file COPYING.LIB.
  */
 
 /** \file bitstream.c Bitstream API for the wma decoder. */
@@ -19,6 +19,7 @@
 #include "error.h"
 #include "string.h"
 #include "wma.h"
+#include "portable_io.h"
 #include "bitstream.h"
 
 static inline uint32_t get_data(const void *table, int i, int size)
@@ -46,7 +47,7 @@ static void alloc_table(struct vlc *vlc, int size)
        if (vlc->table_size > vlc->table_allocated) {
                vlc->table_allocated += (1 << vlc->bits);
                vlc->table = para_realloc(vlc->table,
-                       sizeof(VLC_TYPE) * 2 * vlc->table_allocated);
+                       sizeof(int16_t) * 2 * vlc->table_allocated);
        }
 }
 
@@ -56,7 +57,7 @@ static int build_table(struct vlc *vlc, int table_nb_bits, int nb_codes,
 {
        int i, j, k, n, table_size, table_index, nb, n1, idx;
        uint32_t code;
-       VLC_TYPE(*table)[2];
+       int16_t (*table)[2];
 
        table_size = 1 << table_nb_bits;
        table_index = vlc->table_size;
@@ -164,33 +165,29 @@ void free_vlc(struct vlc *vlc)
  * Parse a vlc code.
  *
  * \param gbc The getbit context structure.
- * \param table The vlc tables to use.
- * \param bits The number of bits which will be read at once.
- *
- * The \a bits parameter must be identical to the \a nb_bits value supplied to
- * \ref init_vlc().
+ * \param vlc The vlc tables to use.
  *
  * \return The vlc code.
  */
-int get_vlc(struct getbit_context *gbc, VLC_TYPE(*table)[2], int bits)
+int get_vlc(struct getbit_context *gbc, const struct vlc *vlc)
 {
        int n, idx, nb_bits, code;
 
-       idx = show_bits(gbc, bits);
-       code = table[idx][0];
-       n = table[idx][1];
+       idx = show_bits(gbc, vlc->bits);
+       code = vlc->table[idx][0];
+       n = vlc->table[idx][1];
        if (n < 0) {
-               skip_bits(gbc, bits);
+               skip_bits(gbc, vlc->bits);
                nb_bits = -n;
                idx = show_bits(gbc, nb_bits) + code;
-               code = table[idx][0];
-               n = table[idx][1];
+               code = vlc->table[idx][0];
+               n = vlc->table[idx][1];
                if (n < 0) {
                        skip_bits(gbc, nb_bits);
                        nb_bits = -n;
                        idx = show_bits(gbc, nb_bits) + code;
-                       code = table[idx][0];
-                       n = table[idx][1];
+                       code = vlc->table[idx][0];
+                       n = vlc->table[idx][1];
                }
        }
        skip_bits(gbc, n);
index 5875b0d090e6e8007d34766ee090950c819188dd..98937e89f2b98f126007d795f8070c0b33508598 100644 (file)
@@ -3,8 +3,7 @@
  *
  * copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at>
  *
- * Licensed under the GNU Lesser General Public License.
- * For licencing details see COPYING.LIB.
+ * Licensed under the GNU Lesser General Public License, see file COPYING.LIB.
  */
 
 /** \file bitstream.h Bitstream structures and inline functions. */
 struct getbit_context {
        /** Start of the internal buffer. */
        const uint8_t *buffer;
-       /** End of the internal buffer. */
-       const uint8_t *buffer_end;
+       /** Length of buffer in bits (always a multiple of 8). */
+       uint32_t num_bits;
        /** Bit counter. */
        int index;
 };
 
-#define VLC_TYPE int16_t
-
 /** A variable length code table. */
 struct vlc {
        /** Number of bits of the table. */
        int bits;
        /** The code and the bits table. */
-       VLC_TYPE(*table)[2];
+       int16_t (*table)[2];
        /** The size of the table. */
        int table_size;
        /** Amount of memory allocated so far. */
@@ -36,8 +33,12 @@ struct vlc {
 static inline uint32_t show_bits(struct getbit_context *gbc, int num)
 {
        int idx = gbc->index;
-       const uint8_t *p = gbc->buffer + (idx >> 3);
-       uint32_t x = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+       const char *p;
+       uint32_t x;
+
+       assert(idx + num <= gbc->num_bits);
+       p = (const char *)gbc->buffer + (idx >> 3);
+       x = read_u32_be(p);
        return (x << (idx & 7)) >> (32 - num);
 }
 
@@ -48,12 +49,13 @@ static inline int get_bits_count(struct getbit_context *gbc)
 
 static inline void skip_bits(struct getbit_context *gbc, int n)
 {
+       assert(gbc->index + n <= gbc->num_bits);
        gbc->index += n;
 }
 
 static inline unsigned int get_bits(struct getbit_context *gbc, int n)
 {
-       unsigned int ret = show_bits(gbc, n);
+       unsigned int ret = show_bits(gbc, n); /* checks n */
        skip_bits(gbc, n);
        return ret;
 }
@@ -61,8 +63,13 @@ static inline unsigned int get_bits(struct getbit_context *gbc, int n)
 /* This is rather hot, we can do better than get_bits(gbc, 1). */
 static inline unsigned int get_bit(struct getbit_context *gbc)
 {
-       int idx = gbc->index++;
-       uint8_t tmp = gbc->buffer[idx >> 3], mask = 1 << (7 - (idx & 7));
+       int idx;
+       uint8_t tmp, mask;
+
+       assert(gbc->index < gbc->num_bits);
+       idx = gbc->index++;
+       tmp = gbc->buffer[idx >> 3];
+       mask = 1 << (7 - (idx & 7));
        return !!(tmp & mask);
 }
 
@@ -81,11 +88,11 @@ static inline void init_get_bits(struct getbit_context *gbc,
                const uint8_t *buffer, int size)
 {
        gbc->buffer = buffer;
-       gbc->buffer_end = buffer + size;
+       gbc->num_bits = size * 8;
        gbc->index = 0;
 }
 
 void init_vlc(struct vlc *vlc, int nb_bits, int nb_codes, const void *bits,
                const void *codes, int codes_size);
 void free_vlc(struct vlc *vlc);
-int get_vlc(struct getbit_context *gbc, VLC_TYPE(*table)[2], int bits);
+int get_vlc(struct getbit_context *gbc, const struct vlc *vlc);
diff --git a/blob.c b/blob.c
index ed684428aba55ae848c58fe8c96af2c900f4d6b9..4ecbc45bb15f42c15342e7900f4de94522ef45bb 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -1,15 +1,13 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file blob.c Macros and functions for blob handling. */
 
 #include <regex.h>
 #include <fnmatch.h>
 #include <osl.h>
+#include <lopsub.h>
 
+#include "server_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "crypt.h"
  * \param obj2 Pointer to the second integer.
  *
  * \return The values required for an osl compare function.
- *
- * \sa osl_compare_func, osl_hash_compare().
  */
 static int uint32_compare(const struct osl_object *obj1, const struct osl_object *obj2)
 {
-       uint32_t d1 = read_u32((const char *)obj1->data);
-       uint32_t d2 = read_u32((const char *)obj2->data);
+       uint32_t d1 = read_u32(obj1->data);
+       uint32_t d2 = read_u32(obj2->data);
 
        if (d1 < d2)
                return 1;
@@ -76,7 +72,6 @@ static struct osl_column_description blob_cols[] = {
 /** Define a pointer to an osl blob table with a canonical name. */
 #define DEFINE_BLOB_TABLE_PTR(table_name) struct osl_table *table_name ## _table;
 
-
 /** Define a blob table. */
 #define INIT_BLOB_TABLE(table_name) \
        DEFINE_BLOB_TABLE_DESC(table_name); \
@@ -90,26 +85,16 @@ INIT_BLOB_TABLE(moods);
 INIT_BLOB_TABLE(playlists);
 /** \endcond blob_table */
 
-/** Flags that may be passed to the \p ls functions of each blob  type. */
-enum blob_ls_flags {
-       /** List both id and name. */
-       BLOB_LS_FLAG_LONG = 1,
-       /** Reverse sort order. */
-       BLOB_LS_FLAG_REVERSE = 2,
-       /** Sort by id instead of name. */
-       BLOB_LS_FLAG_SORT_BY_ID = 4,
-};
-
 static int print_blob(struct osl_table *table, struct osl_row *row,
                const char *name, void *data)
 {
        struct afs_callback_arg *aca = data;
-       uint32_t flags = *(uint32_t *)aca->query.data;
+       bool l_given = SERVER_CMD_OPT_GIVEN(LSMOOD, LONG, aca->lpr);
        struct osl_object obj;
        uint32_t id;
        int ret;
 
-       if (!(flags & BLOB_LS_FLAG_LONG)) {
+       if (!l_given) {
                para_printf(&aca->pbout, "%s\n", name);
                return 0;
        }
@@ -118,19 +103,17 @@ static int print_blob(struct osl_table *table, struct osl_row *row,
                para_printf(&aca->pbout, "cannot list %s\n", name);
                return ret;
        }
-       id = *(uint32_t *)obj.data;
+       id = read_u32(obj.data);
        para_printf(&aca->pbout, "%u\t%s\n", id, name);
        return 1;
 }
 
-static int com_lsblob_callback(struct osl_table *table,
-               struct afs_callback_arg *aca)
+static int com_lsblob_callback(const struct lls_command * const cmd,
+               struct osl_table *table, struct afs_callback_arg *aca)
 {
-       uint32_t flags = *(uint32_t *)aca->query.data;
+       bool i_given, r_given;
        struct pattern_match_data pmd = {
                .table = table,
-               .patterns = {.data = (char *)aca->query.data + sizeof(uint32_t),
-                       .size = aca->query.size - sizeof(uint32_t)},
                .pm_flags = PM_NO_PATTERN_MATCHES_EVERYTHING | PM_SKIP_EMPTY_NAME,
                .match_col_num = BLOBCOL_NAME,
                .data = aca,
@@ -138,53 +121,32 @@ static int com_lsblob_callback(struct osl_table *table,
        };
        int ret;
 
-       if (flags & BLOB_LS_FLAG_REVERSE)
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       pmd.lpr = aca->lpr;
+       assert(ret >= 0);
+       i_given = SERVER_CMD_OPT_GIVEN(LSMOOD, ID_SORT, aca->lpr);
+       r_given = SERVER_CMD_OPT_GIVEN(LSMOOD, REVERSE, aca->lpr);
+
+       if (r_given)
                pmd.pm_flags |= PM_REVERSE_LOOP;
-       if (!(flags & BLOB_LS_FLAG_SORT_BY_ID))
-               pmd.loop_col_num = BLOBCOL_NAME;
-       else
+       if (i_given)
                pmd.loop_col_num = BLOBCOL_ID;
+       else
+               pmd.loop_col_num = BLOBCOL_NAME;
        ret = for_each_matching_row(&pmd);
        if (ret < 0)
                goto out;
-       if (pmd.num_matches == 0 && pmd.patterns.size > 0)
+       if (pmd.num_matches == 0 && lls_num_inputs(aca->lpr) > 0)
                ret = -E_NO_MATCH;
 out:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-static int com_lsblob(afs_callback *f, struct command_context *cc)
+static int com_lsblob(afs_callback *f, const struct lls_command * const cmd,
+               struct command_context *cc, struct lls_parse_result *lpr)
 {
-       uint32_t flags = 0;
-       struct osl_object options = {.data = &flags, .size = sizeof(flags)};
-       int i;
-
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-l")) {
-                       flags |= BLOB_LS_FLAG_LONG;
-                       continue;
-               }
-               if (!strcmp(arg, "-i")) {
-                       flags |= BLOB_LS_FLAG_SORT_BY_ID;
-                       continue;
-               }
-               if (!strcmp(arg, "-r")) {
-                       flags |= BLOB_LS_FLAG_REVERSE;
-                       continue;
-               }
-               break;
-       }
-//     if (argc > i)
-//             return -E_BLOB_SYNTAX;
-       return send_option_arg_callback_request(&options, cc->argc - i,
-               cc->argv + i, f, afs_cb_result_handler, cc);
+       return send_lls_callback_request(f, cmd, lpr, cc);
 }
 
 static int cat_blob(struct osl_table *table, struct osl_row *row,
@@ -202,33 +164,43 @@ static int cat_blob(struct osl_table *table, struct osl_row *row,
        return (ret < 0)? ret : ret2;
 }
 
-static int com_catblob_callback(struct osl_table *table,
-               struct afs_callback_arg *aca)
+static int com_catblob_callback(const struct lls_command * const cmd,
+               struct osl_table *table, struct afs_callback_arg *aca)
 {
        int ret;
        struct pattern_match_data pmd = {
                .table = table,
-               .patterns = aca->query,
                .loop_col_num = BLOBCOL_NAME,
                .match_col_num = BLOBCOL_NAME,
                .pm_flags = PM_SKIP_EMPTY_NAME,
                .data = &aca->fd,
                .action = cat_blob
        };
+
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       pmd.lpr = aca->lpr;
        ret = for_each_matching_row(&pmd);
        if (ret < 0)
-               return ret;
+               goto out;
        if (pmd.num_matches == 0)
                ret = -E_NO_MATCH;
+out:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-static int com_catblob(afs_callback *f, struct command_context *cc)
+static int com_catblob(afs_callback *f, const struct lls_command * const cmd,
+               struct command_context *cc, struct lls_parse_result *lpr)
 {
-       if (cc->argc < 2)
-               return -E_BLOB_SYNTAX;
-       return send_standard_callback_request(cc->argc - 1, cc->argv + 1, f,
-               afs_cb_result_handler, cc);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx));
+
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       return send_lls_callback_request(f, cmd, lpr, cc);
 }
 
 static int remove_blob(struct osl_table *table, struct osl_row *row,
@@ -236,6 +208,7 @@ static int remove_blob(struct osl_table *table, struct osl_row *row,
 {
        struct afs_callback_arg *aca = data;
        int ret = osl(osl_del_row(table, row));
+
        if (ret < 0) {
                para_printf(&aca->pbout, "cannot remove %s\n", name);
                return ret;
@@ -243,19 +216,22 @@ static int remove_blob(struct osl_table *table, struct osl_row *row,
        return 1;
 }
 
-static int com_rmblob_callback(struct osl_table *table,
-               struct afs_callback_arg *aca)
+static int com_rmblob_callback(const struct lls_command * const cmd,
+               struct osl_table *table, struct afs_callback_arg *aca)
 {
        int ret;
        struct pattern_match_data pmd = {
                .table = table,
-               .patterns = aca->query,
                .loop_col_num = BLOBCOL_NAME,
                .match_col_num = BLOBCOL_NAME,
                .pm_flags = PM_SKIP_EMPTY_NAME,
                .data = aca,
                .action = remove_blob
        };
+
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       pmd.lpr = aca->lpr;
        ret = for_each_matching_row(&pmd);
        if (ret < 0)
                goto out;
@@ -267,24 +243,31 @@ static int com_rmblob_callback(struct osl_table *table,
                ret = afs_event(BLOB_REMOVE, NULL, table);
        }
 out:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-static int com_rmblob(afs_callback *f, struct command_context *cc)
+static int com_rmblob(afs_callback *f, const struct lls_command * const cmd,
+               struct command_context *cc, struct lls_parse_result *lpr)
 {
-       if (cc->argc < 2)
-               return -E_MOOD_SYNTAX;
-       return send_option_arg_callback_request(NULL, cc->argc - 1, cc->argv + 1, f,
-               afs_cb_result_handler, cc);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx));
+
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       return send_lls_callback_request(f, cmd, lpr, cc);
 }
 
-static int com_addblob_callback(struct osl_table *table,
-               struct afs_callback_arg *aca)
+static int com_addblob_callback(__a_unused const struct lls_command * const cmd,
+               struct osl_table *table, struct afs_callback_arg *aca)
 {
        struct osl_object objs[NUM_BLOB_COLUMNS];
        char *name = aca->query.data;
        size_t name_len = strlen(name) + 1;
-       uint32_t id;
+       uint32_t id = (uint32_t)-1; /* STFU, gcc */
+       char id_buf[sizeof(id)];
        unsigned num_rows;
        int ret;
 
@@ -292,10 +275,15 @@ static int com_addblob_callback(struct osl_table *table,
        if (ret < 0)
                goto out;
        if (!num_rows) { /* this is the first entry ever added */
-               /* insert dummy row containing the id */
-               id = 2; /* this entry will be entry #1, so 2 is the next */
-               objs[BLOBCOL_ID].data = &id;
-               objs[BLOBCOL_ID].size = sizeof(id);
+               /*
+                * Insert dummy row containing the next free ID. Since we are
+                * about to insert the first blob with ID 1, the next free ID
+                * will be 2.
+                */
+               id = 2U;
+               write_u32(id_buf, id);
+               objs[BLOBCOL_ID].data = id_buf;
+               objs[BLOBCOL_ID].size = sizeof(id_buf);
                objs[BLOBCOL_NAME].data = "";
                objs[BLOBCOL_NAME].size = 1;
                objs[BLOBCOL_DEF].data = "";
@@ -314,7 +302,7 @@ static int com_addblob_callback(struct osl_table *table,
                        ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj));
                        if (ret < 0)
                                goto out;
-                       id = *(uint32_t *)obj.data;
+                       id = read_u32(obj.data);
                        obj.data = name + name_len;
                        obj.size = aca->query.size - name_len;
                        ret = osl(osl_update_object(table, row, BLOBCOL_DEF, &obj));
@@ -329,15 +317,17 @@ static int com_addblob_callback(struct osl_table *table,
                ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj));
                if (ret < 0)
                        goto out;
-               id = *(uint32_t *)obj.data + 1;
-               obj.data = &id;
+               id = read_u32(obj.data) + 1;
+               write_u32(id_buf, id);
+               obj.data = &id_buf;
                ret = osl(osl_update_object(table, row, BLOBCOL_ID, &obj));
                if (ret < 0)
                        goto out;
        }
        id--;
-       objs[BLOBCOL_ID].data = &id;
-       objs[BLOBCOL_ID].size = sizeof(id);
+       write_u32(id_buf, id);
+       objs[BLOBCOL_ID].data = &id_buf;
+       objs[BLOBCOL_ID].size = sizeof(id_buf);
        objs[BLOBCOL_NAME].data = name;
        objs[BLOBCOL_NAME].size = name_len;
        objs[BLOBCOL_DEF].data = name + name_len;
@@ -395,15 +385,16 @@ again:
  *
  * This function is called from the addblob command handlers to instruct the
  * afs process to store the input in a blob table. Input is read and decrypted
- * from the file descriptor given by cc and appended to arg_obj, which contains
+ * from the file descriptor given by cc and appended to a buffer which also contains
  * the name of the blob to create. The combined buffer is made available to the
  * afs process via the callback method.
  */
-static int stdin_command(struct command_context *cc, struct osl_object *arg_obj,
-               afs_callback *f)
+static int stdin_command(struct command_context *cc,
+               struct lls_parse_result *lpr, afs_callback *f)
 {
        struct osl_object query, stdin_obj;
        int ret;
+       size_t len = strlen(lls_input(0, lpr));
 
        ret = send_sb(&cc->scc, NULL, 0, SBD_AWAITING_DATA, false);
        if (ret < 0)
@@ -411,11 +402,11 @@ static int stdin_command(struct command_context *cc, struct osl_object *arg_obj,
        ret = fd2buf(&cc->scc, &stdin_obj);
        if (ret < 0)
                return ret;
-       query.size = arg_obj->size + stdin_obj.size;
+       query.size = len + 1 + stdin_obj.size;
        query.data = para_malloc(query.size);
-       memcpy(query.data, arg_obj->data, arg_obj->size);
+       memcpy(query.data, lls_input(0, lpr), len + 1);
        if (stdin_obj.size > 0)
-               memcpy((char *)query.data + arg_obj->size, stdin_obj.data,
+               memcpy((char *)query.data + len + 1, stdin_obj.data,
                        stdin_obj.size);
        free(stdin_obj.data);
        ret = send_callback_request(f, &query, afs_cb_result_handler, cc);
@@ -423,33 +414,42 @@ static int stdin_command(struct command_context *cc, struct osl_object *arg_obj,
        return ret;
 }
 
-static int com_addblob(afs_callback *f, struct command_context *cc)
+static int com_addblob(afs_callback *f, __a_unused const struct lls_command * const cmd,
+               struct command_context *cc, struct lls_parse_result *lpr)
 {
-       struct osl_object arg_obj;
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
 
-       if (cc->argc != 2)
-               return -E_BLOB_SYNTAX;
-       if (!*cc->argv[1]) /* empty name is reserved for the dummy row */
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       if (!lls_input(0, lpr)[0]) /* empty name is reserved for the dummy row */
                return -E_BLOB_SYNTAX;
-       arg_obj.size = strlen(cc->argv[1]) + 1;
-       arg_obj.data = (char *)cc->argv[1];
-       return stdin_command(cc, &arg_obj, f);
+       return stdin_command(cc, lpr, f);
 }
 
-static int com_mvblob_callback(struct osl_table *table,
-               struct afs_callback_arg *aca)
+static int com_mvblob_callback(const struct lls_command * const cmd,
+               struct osl_table *table, struct afs_callback_arg *aca)
 {
-       char *src = (char *)aca->query.data;
-       struct osl_object obj = {.data = src, .size = strlen(src) + 1};
-       char *dest = src + obj.size;
+       const char *src, *dest;
+       struct osl_object obj;
        struct osl_row *row;
-       int ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
+       int ret;
+
+       ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+       assert(ret >= 0);
+       src = lls_input(0, aca->lpr);
+       dest = lls_input(1, aca->lpr);
+       obj.data = (char *)src;
+       obj.size = strlen(src) + 1;
+       ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
 
        if (ret < 0) {
                para_printf(&aca->pbout, "cannot find source blob %s\n", src);
                goto out;
        }
-       obj.data = dest;
+       obj.data = (char *)dest;
        obj.size = strlen(dest) + 1;
        ret = osl(osl_update_object(table, row, BLOBCOL_NAME, &obj));
        if (ret < 0) {
@@ -459,26 +459,35 @@ static int com_mvblob_callback(struct osl_table *table,
        }
        ret = afs_event(BLOB_RENAME, NULL, table);
 out:
+       lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
 
-static int com_mvblob(afs_callback *f, struct command_context *cc)
+static int com_mvblob(afs_callback *f, const struct lls_command * const cmd,
+               struct command_context *cc, struct lls_parse_result *lpr)
 {
-       if (cc->argc != 3)
-               return -E_MOOD_SYNTAX;
-       return send_option_arg_callback_request(NULL, cc->argc - 1,
-               cc->argv + 1, f, afs_cb_result_handler, cc);
+       char *errctx;
+       int ret = lls(lls_check_arg_count(lpr, 2, 2, &errctx));
+
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       return send_lls_callback_request(f, cmd, lpr, cc);
 }
 
-#define DEFINE_BLOB_COMMAND(cmd_name, table_name, cmd_prefix) \
-       static int com_ ## cmd_name ## cmd_prefix ## _callback(struct afs_callback_arg *aca) \
+#define DEFINE_BLOB_COMMAND(cmd_name, c_cmd_name, table_name, short_name, c_short_name) \
+       static int com_ ## cmd_name ## short_name ## _callback(struct afs_callback_arg *aca) \
        { \
-               return com_ ## cmd_name ## blob_callback(table_name ## _table, aca); \
+               const struct lls_command *cmd = SERVER_CMD_CMD_PTR(c_cmd_name ## c_short_name); \
+               return com_ ## cmd_name ## blob_callback(cmd, table_name ## _table, aca); \
        } \
-       int com_ ## cmd_name ## cmd_prefix(struct command_context *cc) \
+       static int com_ ## cmd_name ## short_name(struct command_context *cc, struct lls_parse_result *lpr) \
        { \
-               return com_ ## cmd_name ## blob(com_ ## cmd_name ## cmd_prefix ## _callback, cc); \
-       }
+               const struct lls_command *cmd = SERVER_CMD_CMD_PTR(c_cmd_name ## c_short_name); \
+               return com_ ## cmd_name ## blob(com_ ## cmd_name ## short_name ## _callback, cmd, cc, lpr); \
+       } \
+       EXPORT_SERVER_CMD_HANDLER(cmd_name ## short_name);
 
 static int blob_get_name_by_id(struct osl_table *table, uint32_t id,
                char **name)
@@ -511,7 +520,6 @@ static int blob_get_name_by_id(struct osl_table *table, uint32_t id,
                return blob_get_name_by_id(table_name ## _table, id, name); \
        }
 
-
 static int blob_get_def_by_name(struct osl_table *table, char *name,
                struct osl_object *def)
 {
@@ -563,6 +571,7 @@ static int blob_get_name_and_def_by_row(struct osl_table *table,
 {
        struct osl_object obj;
        int ret = osl(osl_get_object(table, row, BLOBCOL_NAME, &obj));
+
        if (ret < 0)
                return ret;
        *name = obj.data;
@@ -598,6 +607,7 @@ static int blob_open(struct osl_table **table,
                const char *dir)
 {
        int ret;
+
        desc->dir = dir;
        ret = osl(osl_open_table(desc, table));
        if (ret >= 0)
@@ -629,25 +639,25 @@ static int blob_open(struct osl_table **table,
 
 
 /** Define all functions for this blob type. */
-#define DEFINE_BLOB_FUNCTIONS(table_name, cmd_prefix) \
+#define DEFINE_BLOB_FUNCTIONS(table_name, short_name, c_short_name) \
        DEFINE_BLOB_OPEN(table_name) \
        DEFINE_BLOB_CLOSE(table_name) \
        DEFINE_BLOB_CREATE(table_name) \
        DEFINE_BLOB_INIT(table_name) \
-       DEFINE_BLOB_COMMAND(ls, table_name, cmd_prefix) \
-       DEFINE_BLOB_COMMAND(cat, table_name, cmd_prefix) \
-       DEFINE_BLOB_COMMAND(add, table_name, cmd_prefix) \
-       DEFINE_BLOB_COMMAND(rm, table_name, cmd_prefix) \
-       DEFINE_BLOB_COMMAND(mv, table_name, cmd_prefix) \
-       DEFINE_GET_NAME_BY_ID(table_name, cmd_prefix); \
-       DEFINE_GET_DEF_BY_ID(table_name, cmd_prefix); \
-       DEFINE_GET_DEF_BY_NAME(table_name, cmd_prefix); \
-       DEFINE_GET_NAME_AND_DEF_BY_ROW(table_name, cmd_prefix); \
+       DEFINE_BLOB_COMMAND(ls, LS, table_name, short_name, c_short_name) \
+       DEFINE_BLOB_COMMAND(cat, CAT, table_name, short_name, c_short_name) \
+       DEFINE_BLOB_COMMAND(add, ADD, table_name, short_name, c_short_name) \
+       DEFINE_BLOB_COMMAND(rm, RM, table_name, short_name, c_short_name) \
+       DEFINE_BLOB_COMMAND(mv, MV, table_name, short_name, c_short_name) \
+       DEFINE_GET_NAME_BY_ID(table_name, short_name); \
+       DEFINE_GET_DEF_BY_ID(table_name, short_name); \
+       DEFINE_GET_DEF_BY_NAME(table_name, short_name); \
+       DEFINE_GET_NAME_AND_DEF_BY_ROW(table_name, short_name); \
 
 /* doxygen isn't smart enough to recognize these */
 /** \cond blob_function */
-DEFINE_BLOB_FUNCTIONS(lyrics, lyr);
-DEFINE_BLOB_FUNCTIONS(images, img);
-DEFINE_BLOB_FUNCTIONS(moods, mood);
-DEFINE_BLOB_FUNCTIONS(playlists, pl);
+DEFINE_BLOB_FUNCTIONS(lyrics, lyr, LYR);
+DEFINE_BLOB_FUNCTIONS(images, img, IMG);
+DEFINE_BLOB_FUNCTIONS(moods, mood, MOOD);
+DEFINE_BLOB_FUNCTIONS(playlists, pl, PL);
 /** \endcond blob_function */
index b0cd6665416627806d922cda50fac2e2eeeab17f..8a3175133e69aa57eff7da5155341cbf05532d03 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file buffer_tree.c Buffer tree and buffer pool implementations. */
 #include <regex.h>
@@ -905,7 +901,7 @@ size_t btr_get_output_queue_size(struct btr_node *btrn)
  * \return \p -ENOTSUP if no parent node of \a btrn understands \a command.
  * Otherwise the return value of the command handler is returned.
  *
- * \sa \ref receiver::execute, filter::execute, writer::execute.
+ * \sa \ref receiver::execute, \ref filter::execute, \ref writer::execute.
  */
 int btr_exec_up(struct btr_node *btrn, const char *command, char **value_result)
 {
@@ -935,7 +931,7 @@ int btr_exec_up(struct btr_node *btrn, const char *command, char **value_result)
  *
  * \return A pointer to the \a context address specified at node creation time.
  *
- * \sa btr_new_node(), struct \ref btr_node_description.
+ * \sa \ref btr_new_node(), struct \ref btr_node_description.
  */
 void *btr_context(struct btr_node *btrn)
 {
index 3ee469c91b3fe56583b305fbb5dcb92d6ac9a4aa..34535219364a6cdc2ce32efcc04ca2d44c2c20c2 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /**
  * \file buffer_tree.h Buffer tree management.
index 1a47f946f3fb01829ae0d84caacb7461d0d22580..89ebdacc0805e9575acea92d09ed3b2dd81ebac3 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file check_wav.c Detect and delete a wav header. */
 
@@ -15,6 +11,7 @@
 #include "buffer_tree.h"
 #include "error.h"
 #include "check_wav.h"
+#include "portable_io.h"
 
 /** Length of a standard wav header. */
 #define WAV_HEADER_LEN 44
@@ -159,9 +156,9 @@ int check_wav_post_select(struct check_wav_context *cwc)
        PARA_INFO_LOG("found wav header\n");
        cwc->state = CWS_HAVE_HEADER;
        /* Only set those values which have not already been set. */
-       cwc->channels = (unsigned)a[22];
-       cwc->sample_rate = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24);
-       bps = a[34] + ((unsigned)a[35] << 8);
+       cwc->channels = a[22];
+       cwc->sample_rate = read_u32(a + 24);
+       bps = read_u16(a + 34);
        if (bps != 8 && bps != 16) {
                PARA_WARNING_LOG("%u bps not supported, assuming 16\n",
                        bps);
index 0957fe0364c914b7f50c3c1148ef16cbb2350f31..79b11962baea9b3fe743f08ad68bca6a70e552bc 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file check_wav.h Detect, process and cut a wav header. */
 
@@ -29,22 +25,19 @@ struct wav_params {
        int sample_format_given;
 };
 
-/**
- * Copy the wav parameters.
- *
- * \param dst Usually a pointer to struct wav_params.
- * \param src Usually a pointer to some args_info struct.
- *
- * This can not be implemented as a function since the type of the structure
- * pointed to by \a src depends on the application.
- */
-#define COPY_WAV_PARMS(dst, src) \
-       (dst)->channels_arg = (src)->channels_arg; \
-       (dst)->channels_given = (src)->channels_given; \
-       (dst)->sample_rate_arg = (src)->sample_rate_arg; \
-       (dst)->sample_rate_given = (src)->sample_rate_given; \
-       (dst)->sample_format_arg = (src)->sample_format_arg; \
-       (dst)->sample_format_given = (src)->sample_format_given;
+#define LLS_COPY_WAV_PARMS(_dst, _pfx, _lpr) \
+       (_dst)->channels_given = lls_opt_given(lls_opt_result( \
+               _pfx ## _OPT_CHANNELS, (_lpr))); \
+       (_dst)->sample_rate_given = lls_opt_given(lls_opt_result( \
+               _pfx ## _OPT_SAMPLE_RATE, (_lpr))); \
+       (_dst)->sample_format_given = lls_opt_given(lls_opt_result( \
+               _pfx ## _OPT_SAMPLE_FORMAT, (_lpr))); \
+       (_dst)->channels_arg = lls_uint32_val(0, lls_opt_result( \
+               _pfx ## _OPT_CHANNELS, (_lpr))); \
+       (_dst)->sample_rate_arg = lls_uint32_val(0, lls_opt_result( \
+               _pfx ## _OPT_SAMPLE_RATE, (_lpr))); \
+       (_dst)->sample_format_arg = lls_uint32_val(0, lls_opt_result( \
+               _pfx ## _OPT_SAMPLE_FORMAT, (_lpr)));
 
 struct check_wav_context *check_wav_init(struct btr_node *parent,
                struct btr_node *child, struct wav_params *params,
index c9f47b2b009174f92d813e9ebd2c9e02eb25d09c..08f57e9d1ecf9bae1cadead0757fe29418c446c3 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file chunk_queue.c Queuing functions for paraslash senders. */
 
index 1e7fed2d4f5b5fdeef4680169a31fe480ecbb72b..bc92ab0546a53cab4482d0b9f5203cde52614900 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file chunk_queue.h Exported symbols from chunk_queue.c. */
 
index f1100df49e144b873779ca84d226c1829cb1c1a3..3edaab5dbbf2ec6f086a9483500e431069770b28 100644 (file)
--- a/client.c
+++ b/client.c
@@ -1,19 +1,16 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file client.c The client program used to connect to para_server. */
 
 #include <regex.h>
 #include <signal.h>
+#include <lopsub.h>
 
+#include "client.lsg.h"
 #include "para.h"
 #include "list.h"
 #include "sched.h"
 #include "crypt.h"
-#include "client.cmdline.h"
 #include "string.h"
 #include "stdin.h"
 #include "stdout.h"
@@ -36,8 +33,7 @@ __printf_2_3 void (*para_log)(int, const char*, ...) = stderr_log;
 
 #ifdef HAVE_READLINE
 #include "interactive.h"
-#include "server.completion.h"
-#include "afs.completion.h"
+#include "server_cmd.lsg.h"
 
 struct exec_task {
        struct task *task;
@@ -78,17 +74,52 @@ out:
        return 0;
 }
 
-static int make_client_argv(const char *line)
+/* Called from the line handler and the completers. This overwrites ct->lpr. */
+static int create_merged_lpr(const char *line)
 {
-       int ret;
+       const struct lls_command *cmd = CLIENT_CMD_PTR;
+       int argc, ret;
+       char *cmdline, **argv, *errctx;
+       struct lls_parse_result *argv_lpr;
+       static struct lls_parse_result *orig_lpr;
 
-       free_argv(ct->conf.inputs);
-       ret = create_argv(line, " ", &ct->conf.inputs);
-       if (ret >= 0)
-               ct->conf.inputs_num = ret;
+       if (!orig_lpr)
+               orig_lpr = ct->lpr;
+       ct->lpr = NULL;
+       cmdline = make_message("-- %s", line);
+       ret = create_shifted_argv(cmdline, " ", &argv);
+       free(cmdline);
+       if (ret < 0)
+               return ret;
+       argc = ret;
+       if (argc == 2) { /* no words (only blanks in line) */
+               free_argv(argv);
+               return 0;
+       }
+       argv[0] = para_strdup("--");
+       /*
+        * The original lpr for the interactive session has no non-option
+        * arguments. We create a fresh lpr from the words in "line" and merge
+        * it with the original lpr.
+        */
+       ret = lls(lls_parse(argc, argv, cmd, &argv_lpr, &errctx));
+       free_argv(argv);
+       if (ret < 0)
+               goto fail;
+       ret = lls(lls_merge(orig_lpr, argv_lpr, cmd, &ct->lpr, &errctx));
+       lls_free_parse_result(argv_lpr, cmd);
+       if (ret < 0)
+               goto fail;
+       return 1;
+fail:
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       free(errctx);
+       assert(ret < 0);
        return ret;
 }
 
+/* called from completers */
 static int execute_client_command(const char *cmd, char **result)
 {
        int ret;
@@ -97,9 +128,11 @@ static int execute_client_command(const char *cmd, char **result)
                .result_buf = para_strdup(""),
                .result_size = 1,
        };
+       struct lls_parse_result *old_lpr = ct->lpr;
+
        *result = NULL;
-       ret = make_client_argv(cmd);
-       if (ret < 0)
+       ret = create_merged_lpr(cmd);
+       if (ret <= 0)
                goto out;
        exec_task.btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "exec_collect"));
@@ -114,6 +147,8 @@ static int execute_client_command(const char *cmd, char **result)
                goto out;
        schedule(&command_sched);
        sched_shutdown(&command_sched);
+       lls_free_parse_result(ct->lpr, CLIENT_CMD_PTR);
+       ct->lpr = old_lpr;
        *result = exec_task.result_buf;
        btr_remove_node(&exec_task.btrn);
        ret = 1;
@@ -174,7 +209,7 @@ static void complete_lsblob(const char *blob_type,
                struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-i", "-l", "-r", NULL};
+       char *opts[] = {LSG_SERVER_CMD_LSIMG_OPTS, NULL};
 
        if (ci->word[0] == '-')
                return i9e_complete_option(opts, ci, cr);
@@ -213,23 +248,28 @@ I9E_DUMMY_COMPLETER(tasks);
 static struct i9e_completer completers[];
 
 static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
 {
-       result->matches = i9e_complete_commands(ci->word, completers);
+       char *opts[] = {LSG_SERVER_CMD_HELP_OPTS, NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, completers);
 }
 
-static void version_completer(struct i9e_completion_info *ci,
+static void stat_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-v", NULL};
+       char *opts[] = {LSG_SERVER_CMD_STAT_OPTS, NULL};
        i9e_complete_option(opts, ci, cr);
 }
 
-static void stat_completer(struct i9e_completion_info *ci,
+static void version_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-n=", "-p", NULL};
-       //PARA_CRIT_LOG("word: %s\n", ci->word);
+       char *opts[] = {LSG_SERVER_CMD_VERSION_OPTS, NULL};
        i9e_complete_option(opts, ci, cr);
 }
 
@@ -266,7 +306,7 @@ static void sender_completer(struct i9e_completion_info *ci,
 static void add_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-a", "-l", "-f", "-v", "--", NULL};
+       char *opts[] = {LSG_SERVER_CMD_ADD_OPTS, NULL};
 
        if (ci->word[0] == '-')
                i9e_complete_option(opts, ci, cr);
@@ -276,11 +316,7 @@ static void add_completer(struct i9e_completion_info *ci,
 static void ls_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {
-               "--", "-l", "-l=s", "-l=l", "-l=v", "-l=p", "-l=m", "-l=c",
-               "-p", "-a", "-r", "-d", "-s=p", "-s=l", "-s=s", "-s=n", "-s=f",
-               "-s=c", "-s=i", "-s=y", "-s=b", "-s=d", "-s=a", "-F", "-b", NULL
-       };
+       char *opts[] = {LSG_SERVER_CMD_LS_OPTS, NULL};
        if (ci->word[0] == '-')
                i9e_complete_option(opts, ci, cr);
        cr->filename_completion_desired = true;
@@ -323,7 +359,7 @@ out:
 static void lsatt_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-i", "-l", "-r", NULL};
+       char *opts[] = {LSG_SERVER_CMD_LSATT_OPTS, NULL};
        i9e_complete_option(opts, ci, cr);
 }
 
@@ -342,14 +378,14 @@ static void rmatt_completer(struct i9e_completion_info *ci,
 static void check_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-a", "-m", "-p", NULL};
+       char *opts[] = {LSG_SERVER_CMD_CHECK_OPTS, NULL};
        i9e_complete_option(opts, ci, cr);
 }
 
 static void rm_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-v", "-f", "-p", NULL};
+       char *opts[] = {LSG_SERVER_CMD_RM_OPTS, NULL};
 
        if (ci->word[0] == '-') {
                i9e_complete_option(opts, ci, cr);
@@ -361,7 +397,7 @@ static void rm_completer(struct i9e_completion_info *ci,
 static void touch_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-n=", "-l=", "-y=", "-i=", "-a=", "-v", "-p", NULL};
+       char *opts[] = {LSG_SERVER_CMD_TOUCH_OPTS, NULL};
 
        if (ci->word[0] == '-')
                i9e_complete_option(opts, ci, cr);
@@ -371,7 +407,7 @@ static void touch_completer(struct i9e_completion_info *ci,
 static void cpsi_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-a", "-y", "-i", "-l", "-n", "-v", NULL};
+       char *opts[] = {LSG_SERVER_CMD_CPSI_OPTS, NULL};
 
        if (ci->word[0] == '-')
                i9e_complete_option(opts, ci, cr);
@@ -447,9 +483,13 @@ DEFINE_BLOB_COMPLETER(mv, pl)
 static int client_i9e_line_handler(char *line)
 {
        int ret;
+       static bool first = true;
 
-       PARA_DEBUG_LOG("line: %s\n", line);
-       ret = make_client_argv(line);
+       if (first)
+               first = false;
+       else
+               lls_free_parse_result(ct->lpr, CLIENT_CMD_PTR);
+       ret = create_merged_lpr(line);
        if (ret <= 0)
                return ret;
        ret = client_connect(ct, &sched, NULL, NULL);
@@ -460,15 +500,16 @@ static int client_i9e_line_handler(char *line)
 }
 
 static struct i9e_completer completers[] = {
-       SERVER_COMPLETERS
-       AFS_COMPLETERS
+#define LSG_SERVER_CMD_CMD(_name) {.name = #_name, \
+       .completer = _name ## _completer}
+       LSG_SERVER_CMD_SUBCOMMANDS
+#undef LSG_SERVER_CMD_CMD
        {.name = NULL}
 };
 
 __noreturn static void interactive_session(void)
 {
        int ret;
-       char *history_file;
        struct sigaction act;
        struct i9e_client_info ici = {
                .fds = {0, 1, 2},
@@ -479,16 +520,15 @@ __noreturn static void interactive_session(void)
        };
 
        PARA_NOTICE_LOG("\n%s\n", version_text("client"));
-       if (ct->conf.history_file_given)
-               history_file = para_strdup(ct->conf.history_file_arg);
+       if (CLIENT_OPT_GIVEN(HISTORY_FILE, ct->lpr))
+               ici.history_file = para_strdup(CLIENT_OPT_STRING_VAL(
+                       HISTORY_FILE, ct->lpr));
        else {
                char *home = para_homedir();
-               history_file = make_message("%s/.paraslash/client.history",
+               ici.history_file = make_message("%s/.paraslash/client.history",
                        home);
                free(home);
        }
-       ici.history_file = history_file;
-
        act.sa_handler = i9e_signal_dispatch;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
@@ -512,7 +552,9 @@ out:
 
 __noreturn static void print_completions(void)
 {
-       int ret = i9e_print_completions(completers);
+       int ret;
+
+       ret = i9e_print_completions(completers);
        exit(ret <= 0? EXIT_FAILURE : EXIT_SUCCESS);
 }
 
@@ -575,19 +617,20 @@ static struct supervisor_task supervisor_task;
  *
  * \return EXIT_SUCCESS or EXIT_FAILURE
  *
- * \sa client_open(), stdin.c, stdout.c, para_client(1), para_server(1)
+ * \sa \ref client_open(), \ref stdin.c, \ref stdout.c, para_client(1),
+ * para_server(1).
  */
 int main(int argc, char *argv[])
 {
        int ret;
 
-       init_random_seed_or_die();
+       crypt_init();
        sched.default_timeout.tv_sec = 1;
 
        ret = client_parse_config(argc, argv, &ct, &client_loglevel);
        if (ret < 0)
                goto out;
-       if (ct->conf.complete_given)
+       if (CLIENT_OPT_GIVEN(COMPLETE, ct->lpr))
                print_completions();
        if (ret == 0)
                interactive_session(); /* does not return */
@@ -627,6 +670,7 @@ int main(int argc, char *argv[])
                }
        }
        sched_shutdown(&sched);
+       crypt_shutdown();
 out:
        if (ret < 0)
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
index 2f257221510b1cb0b0848eb615d1dfb894f0cc81..7ba56b9168c500bd4f596c5ba9db82ccef3347c5 100644 (file)
--- a/client.h
+++ b/client.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file client.h Common client functions and exported symbols from client_common.c. */
 
@@ -38,10 +34,8 @@ struct client_task {
        struct btr_node *btrn[2];
        /** The hash value of the decrypted challenge. */
        unsigned char *challenge_hash;
-       /** The configuration (including the command). */
-       struct client_args_info conf;
-       /** The config file for client options. */
-       char *config_file;
+       /** The parsed command line (including the command). */
+       struct lls_parse_result *lpr;
        /** The RSA private key. */
        char *key_file;
        /** Paraslash user name. */
@@ -52,6 +46,16 @@ struct client_task {
        char **features;
 };
 
+#define CLIENT_CMD_PTR (lls_cmd(0, client_suite))
+#define CLIENT_OPT_RESULT(_name, _lpr) \
+       (lls_opt_result(LSG_CLIENT_PARA_CLIENT_OPT_ ## _name, _lpr))
+#define CLIENT_OPT_GIVEN(_name, _lpr) \
+       (lls_opt_given(CLIENT_OPT_RESULT(_name, _lpr)))
+#define CLIENT_OPT_UINT32_VAL(_name, _lpr) \
+       (lls_uint32_val(0, CLIENT_OPT_RESULT(_name, _lpr)))
+#define CLIENT_OPT_STRING_VAL(_name, _lpr) \
+       (lls_string_val(0, CLIENT_OPT_RESULT(_name, _lpr)))
+
 void client_close(struct client_task *ct);
 int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
                int *loglevel);
index eea7510fd0589e86fb4058dd7f4361f3c79901aa..c25da96b169126ab36f5b6e7f95a2161d19a3aa9 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file client_common.c Common functions of para_client and para_audiod. */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "client.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "list.h"
+#include "lsu.h"
 #include "sched.h"
 #include "crypt.h"
 #include "net.h"
 #include "fd.h"
 #include "sideband.h"
 #include "string.h"
-#include "client.cmdline.h"
 #include "client.h"
 #include "buffer_tree.h"
 #include "version.h"
-#include "ggo.h"
 
 /** The size of the receiving buffer. */
 #define CLIENT_BUFSIZE 4000
@@ -44,9 +41,8 @@ void client_close(struct client_task *ct)
        if (!ct)
                return;
        free(ct->user);
-       free(ct->config_file);
        free(ct->key_file);
-       client_cmdline_parser_free(&ct->conf);
+       lls_free_parse_result(ct->lpr, CLIENT_CMD_PTR);
        free(ct->challenge_hash);
        sb_free(ct->sbc[0]);
        sb_free(ct->sbc[1]);
@@ -54,13 +50,12 @@ void client_close(struct client_task *ct)
 }
 
 /*
- * The preselect hook for server commands.
+ * This function asks the scheduler to monitor a file descriptor which
+ * corresponds to an active connection. The descriptor is monitored for either
+ * reading or writing, depending on the state of the connection.
  *
- * The task pointer must contain a pointer to the initialized client data
- * structure as it is returned by client_open().
- *
- * This function checks the state of the connection and adds the file descriptor
- * of the connection to the read or write fd set of s accordingly.
+ * The context pointer is assumed to refer to a client task structure that was
+ * initialized earlier by client_open().
  */
 static void client_pre_select(struct sched *s, void *context)
 {
@@ -90,7 +85,7 @@ static void client_pre_select(struct sched *s, void *context)
                        else if (ret > 0)
                                para_fd_set(ct->scc.fd, &s->wfds, &s->max_fileno);
                }
-               /* fall though */
+               __attribute__ ((fallthrough));
        case CL_EXECUTING:
                if (ct->btrn[0]) {
                        ret = btr_node_status(ct->btrn[0], 0, BTR_NT_ROOT);
@@ -241,38 +236,38 @@ out:
        return ret;
 }
 
-static bool has_feature(const char *feature, struct client_task *ct)
-{
-       return find_arg(feature, ct->features) >= 0? true : false;
-}
-
 static int send_sb_command(struct client_task *ct)
 {
        int i;
        char *command, *p;
        size_t len = 0;
+       unsigned num_inputs = lls_num_inputs(ct->lpr);
 
        if (ct->sbc[1])
                return send_sb(ct, 0, NULL, 0, 0, false);
 
-       for (i = 0; i < ct->conf.inputs_num; i++)
-               len += strlen(ct->conf.inputs[i]) + 1;
+       for (i = 0; i < num_inputs; i++)
+               len += strlen(lls_input(i, ct->lpr)) + 1;
        p = command = para_malloc(len);
-       for (i = 0; i < ct->conf.inputs_num; i++) {
-               strcpy(p, ct->conf.inputs[i]);
-               p += strlen(ct->conf.inputs[i]) + 1;
+       for (i = 0; i < num_inputs; i++) {
+               const char *str = lls_input(i, ct->lpr);
+               strcpy(p, str);
+               p += strlen(str) + 1;
        }
        PARA_DEBUG_LOG("--> %s\n", command);
        return send_sb(ct, 0, command, len, SBD_COMMAND, false);
 }
 
 /*
- * The post select hook for client commands.
+ * This function reads or writes to the socket file descriptor which
+ * corresponds to an established connection between the client and the server.
+ * It depends on the current state of the connection and on the readiness of
+ * the socket file descriptor which type of I/O is going to be performed.
+ * Besides the initial handshake and authentication, the function sends the
+ * server command and receives the output from the server, if any.
  *
- * Depending on the current state of the connection and the status of the read
- * and write fd sets of s, this function performs the necessary steps to
- * authenticate the connection, to send the command given by t->private_data
- * and to receive para_server's output, if any.
+ * The context pointer refers to a client task structure that was initialized
+ * earlier by client_open().
  */
 static int client_post_select(struct sched *s, void *context)
 {
@@ -297,8 +292,8 @@ static int client_post_select(struct sched *s, void *context)
        case CL_RECEIVED_WELCOME: /* send auth command */
                if (!FD_ISSET(ct->scc.fd, &s->wfds))
                        return 0;
-               sprintf(buf, AUTH_REQUEST_MSG "%s sideband%s", ct->user,
-                       has_feature("aes_ctr128", ct)? ",aes_ctr128" : "");
+               sprintf(buf, AUTH_REQUEST_MSG "%s sideband,aes_ctr128",
+                       ct->user);
                PARA_INFO_LOG("--> %s\n", buf);
                ret = write_buffer(ct->scc.fd, buf);
                if (ret < 0)
@@ -314,7 +309,6 @@ static int client_post_select(struct sched *s, void *context)
                /* decrypted challenge/session key buffer */
                unsigned char crypt_buf[1024];
                struct sb_buffer sbb;
-               bool use_aes;
 
                ret = recv_sb(ct, &s->rfds, &sbb);
                if (ret <= 0)
@@ -326,17 +320,16 @@ static int client_post_select(struct sched *s, void *context)
                }
                n = sbb.iov.iov_len;
                PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n);
-               ret = priv_decrypt(ct->key_file, crypt_buf,
+               ret = apc_priv_decrypt(ct->key_file, crypt_buf,
                        sbb.iov.iov_base, n);
                free(sbb.iov.iov_base);
                if (ret < 0)
                        goto out;
                ct->challenge_hash = para_malloc(HASH_SIZE);
-               hash_function((char *)crypt_buf, CHALLENGE_SIZE, ct->challenge_hash);
-               use_aes = has_feature("aes_ctr128", ct);
-               ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN, use_aes);
-               ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
-                       SESSION_KEY_LEN, use_aes);
+               hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
+               ct->scc.send = sc_new(crypt_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN);
+               ct->scc.recv = sc_new(crypt_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN,
+                       SESSION_KEY_LEN);
                hash_to_asc(ct->challenge_hash, buf);
                PARA_INFO_LOG("--> %s\n", buf);
                ct->status = CL_RECEIVED_CHALLENGE;
@@ -398,7 +391,7 @@ static int client_post_select(struct sched *s, void *context)
                                        btr_consume(ct->btrn[1], sz);
                        }
                }
-               /* fall through */
+               __attribute__ ((fallthrough));
        case CL_EXECUTING:
                if (ct->btrn[0]) {
                        ret = btr_node_status(ct->btrn[0], 0, BTR_NT_ROOT);
@@ -467,12 +460,12 @@ int client_connect(struct client_task *ct, struct sched *s,
                struct btr_node *parent, struct btr_node *child)
 {
        int ret;
+       const char *host = CLIENT_OPT_STRING_VAL(HOSTNAME, ct->lpr);
+       uint32_t port = CLIENT_OPT_UINT32_VAL(SERVER_PORT, ct->lpr);
 
-       PARA_NOTICE_LOG("connecting %s:%d\n", ct->conf.hostname_arg,
-               ct->conf.server_port_arg);
+       PARA_NOTICE_LOG("connecting %s:%u\n", host, port);
        ct->scc.fd = -1;
-       ret = para_connect_simple(IPPROTO_TCP, ct->conf.hostname_arg,
-                                              ct->conf.server_port_arg);
+       ret = para_connect_simple(IPPROTO_TCP, host, port);
        if (ret < 0)
                return ret;
        ct->scc.fd = ret;
@@ -498,13 +491,19 @@ err_out:
        return ret;
 }
 
-__noreturn static void print_help_and_die(struct client_task *ct)
+static void handle_help_flag(struct lls_parse_result *lpr)
 {
-       struct ggo_help h = DEFINE_GGO_HELP(client);
-       bool d = ct->conf.detailed_help_given;
+       char *help;
 
-       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
-       exit(0);
+       if (CLIENT_OPT_GIVEN(DETAILED_HELP, lpr))
+               help = lls_long_help(CLIENT_CMD_PTR);
+       else if (CLIENT_OPT_GIVEN(HELP, lpr))
+               help = lls_short_help(CLIENT_CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       exit(EXIT_SUCCESS);
 }
 
 /**
@@ -528,65 +527,56 @@ __noreturn static void print_help_and_die(struct client_task *ct)
 int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
                int *loglevel)
 {
-       char *home = para_homedir();
-       int ret;
-       struct client_task *ct = para_calloc(sizeof(struct client_task));
+       const struct lls_command *cmd = CLIENT_CMD_PTR;
+       struct lls_parse_result *lpr;
+       int ret, ll;
+       struct client_task *ct;
+       char *kf = NULL, *user, *errctx, *home = para_homedir();
 
-       *ct_ptr = ct;
-       ct->scc.fd = -1;
-       ret = -E_CLIENT_SYNTAX;
-       if (client_cmdline_parser(argc, argv, &ct->conf))
+       ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
+       if (ret < 0)
                goto out;
-       version_handle_flag("client", ct->conf.version_given);
-       if (ct->conf.help_given || ct->conf.detailed_help_given)
-               print_help_and_die(ct);
-
-       ct->config_file = ct->conf.config_file_given?
-               para_strdup(ct->conf.config_file_arg) :
-               make_message("%s/.paraslash/client.conf", home);
-       ret = file_exists(ct->config_file);
-       if (!ret && ct->conf.config_file_given) {
-               ret = -E_NO_CONFIG;
+       version_handle_flag("client", CLIENT_OPT_GIVEN(VERSION, lpr));
+       handle_help_flag(lpr);
+
+       ret = lsu_merge_config_file_options(CLIENT_OPT_STRING_VAL(CONFIG_FILE, lpr),
+               "client.conf", &lpr, cmd, client_suite, 0U /* default flags */);
+       if (ret < 0)
                goto out;
-       }
-       if (ret) {
-               struct client_cmdline_parser_params params = {
-                       .override = 0,
-                       .initialize = 0,
-                       .check_required = 0,
-                       .check_ambiguity = 0,
-                       .print_errors = 0
-               };
-               ret = -E_BAD_CONFIG;
-               if (client_cmdline_parser_config_file(ct->config_file,
-                       &ct->conf, &params))
-                       goto out;
-       }
-       ct->user = ct->conf.user_given?
-               para_strdup(ct->conf.user_arg) : para_logname();
+       /* success */
+       ll = CLIENT_OPT_UINT32_VAL(LOGLEVEL, lpr);
+       if (loglevel)
+               *loglevel = ll;
+       user = CLIENT_OPT_GIVEN(USER, lpr)?
+               para_strdup(CLIENT_OPT_STRING_VAL(USER, lpr)) : para_logname();
 
-       if (ct->conf.key_file_given)
-               ct->key_file = para_strdup(ct->conf.key_file_arg);
+       if (CLIENT_OPT_GIVEN(KEY_FILE, lpr))
+               kf = para_strdup(CLIENT_OPT_STRING_VAL(KEY_FILE, lpr));
        else {
-               ct->key_file = make_message("%s/.paraslash/key.%s",
-                       home, ct->user);
-               if (!file_exists(ct->key_file)) {
-                       free(ct->key_file);
-                       ct->key_file = make_message("%s/.ssh/id_rsa", home);
+               kf = make_message("%s/.paraslash/key.%s", home, user);
+               if (!file_exists(kf)) {
+                       free(kf);
+                       kf = make_message("%s/.ssh/id_rsa", home);
                }
        }
-
-       if (loglevel)
-               *loglevel = get_loglevel_by_name(ct->conf.loglevel_arg);
-       PARA_INFO_LOG("loglevel: %s\n", ct->conf.loglevel_arg);
-       PARA_INFO_LOG("config_file: %s\n", ct->config_file);
-       PARA_INFO_LOG("key_file: %s\n", ct->key_file);
-       ret = ct->conf.inputs_num;
+       PARA_INFO_LOG("user: %s\n", user);
+       PARA_INFO_LOG("key file: %s\n", kf);
+       PARA_INFO_LOG("loglevel: %d\n", ll);
+       ct = para_calloc(sizeof(*ct));
+       ct->scc.fd = -1;
+       ct->lpr = lpr;
+       ct->key_file = kf;
+       ct->user = user;
+       *ct_ptr = ct;
+       ret = lls_num_inputs(lpr);
 out:
        free(home);
        if (ret < 0) {
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-               client_close(ct);
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               lls_free_parse_result(lpr, cmd);
+               free(kf);
                *ct_ptr = NULL;
        }
        return ret;
index c606d98c46aeb4c9a15e76b90ea5856231c6a38e..28c5eabb1ae9a4821b60b638be559e837dc54f36 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file close_on_fork.c Manage a list of fds that should be closed on fork. */
 
@@ -19,7 +15,7 @@ static int initialized;
 /**
  * Describes an element of the close-on-fork list.
  *
- * \sa list.h
+ * \sa \ref list.h.
  */
 struct close_on_fork {
        /** The file descriptor which should be closed after fork(). */
@@ -66,13 +62,7 @@ void del_close_on_fork_list(int fd)
        }
 }
 
-/**
- * Close all fds in the list and destroy all list entries.
- *
- * This function calls close(3) for each fd in the close-on-fork list
- * and empties the list afterwards.
- */
-void close_listed_fds(void)
+static void deplete_cof_list(bool close_fds)
 {
        struct close_on_fork *cof, *tmp;
 
@@ -80,8 +70,32 @@ void close_listed_fds(void)
                return;
        list_for_each_entry_safe(cof, tmp, &close_on_fork_list, node) {
                PARA_DEBUG_LOG("closing fd %d\n", cof->fd);
-               close(cof->fd);
+               if (close_fds)
+                       close(cof->fd);
                list_del(&cof->node);
                free(cof);
        }
 }
+
+/**
+ * Close all fds in the list and destroy all list entries.
+ *
+ * This function calls close(3) for each fd in the close-on-fork list
+ * and empties the list afterwards.
+ *
+ * \sa \ref deplete_close_on_fork_list().
+ */
+void close_listed_fds(void)
+{
+       deplete_cof_list(true);
+}
+
+/**
+ * Remove all listed fds from the close on fork list.
+ *
+ * This is like \ref close_listed_fds() but does not close the fds.
+ */
+void deplete_close_on_fork_list(void)
+{
+       deplete_cof_list(false);
+}
index 1bd0cd15f72ef129f83db2a1ab56400b2b1580df..0dcc2b6cd7c5337e053aff9abc4433fcd8039d07 100644 (file)
@@ -2,3 +2,4 @@
 void del_close_on_fork_list(int fd);
 void add_close_on_fork_list(int fd);
 void close_listed_fds(void);
+void deplete_close_on_fork_list(void);
index 6777ccc971e2590bd2d46879ba428ff1f327f7d8..7b3d6faf90849a92ecbafb98538f277cfef5b02a 100644 (file)
--- a/command.c
+++ b/command.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file command.c Client authentication and server commands. */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "server.lsg.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "crypt.h"
 #include "sideband.h"
 #include "command.h"
-#include "server.cmdline.h"
 #include "string.h"
 #include "afh.h"
 #include "afs.h"
+#include "net.h"
 #include "server.h"
 #include "list.h"
 #include "send.h"
 #include "sched.h"
 #include "vss.h"
-#include "net.h"
 #include "daemon.h"
 #include "fd.h"
 #include "ipc.h"
+#include "server_cmd.lsg.h"
 #include "user_list.h"
-#include "server.command_list.h"
-#include "afs.command_list.h"
 #include "signal.h"
 #include "version.h"
 
-typedef int server_command_handler_t(struct command_context *);
-static server_command_handler_t SERVER_COMMAND_HANDLERS;
-server_command_handler_t AFS_COMMAND_HANDLERS;
-
-/* Defines one command of para_server. */
-struct server_command {
-       /* The name of the command. */
-       const char *name;
-       /* Pointer to the function that handles the command. */
-       server_command_handler_t *handler;
-       /* The privileges a user must have to execute this command. */
-       unsigned int perms;
-       /* One-line description of the command. */
-       const char *description;
-       /* Summary of the command line options. */
-       const char *usage;
-       /* The long help text. */
-       const char *help;
-};
-
-static struct server_command afs_cmds[] = {DEFINE_AFS_CMD_ARRAY};
-static struct server_command server_cmds[] = {DEFINE_SERVER_CMD_ARRAY};
+#define SERVER_CMD_AUX_INFO(_arg) _arg,
+static const unsigned server_command_perms[] = {LSG_SERVER_CMD_AUX_INFOS};
+#undef SERVER_CMD_AUX_INFO
+#define SERVER_CMD_AUX_INFO(_arg) #_arg,
+static const char * const server_command_perms_txt[] = {LSG_SERVER_CMD_AUX_INFOS};
+#undef SERVER_CMD_AUX_INFO
 
 /** Commands including options must be shorter than this. */
 #define MAX_COMMAND_LEN 32768
 
 extern int mmd_mutex;
 extern struct misc_meta_data *mmd;
-extern struct sender senders[];
 int send_afs_status(struct command_context *cc, int parser_friendly);
+static bool subcmd_should_die;
 
-static void dummy(__a_unused int s)
+static void command_handler_sighandler(int s)
 {
+       if (s != SIGTERM)
+               return;
+       PARA_EMERG_LOG("terminating on signal %d\n", SIGTERM);
+       subcmd_should_die = true;
 }
 
 /*
@@ -93,21 +78,6 @@ static char *vss_status_tohuman(unsigned int flags)
        return para_strdup("paused");
 }
 
-/*
- * return human readable permission string. Never returns NULL.
- */
-static char *cmd_perms_itohuman(unsigned int perms)
-{
-       char *msg = para_malloc(5 * sizeof(char));
-
-       msg[0] = perms & AFS_READ? 'a' : '-';
-       msg[1] = perms & AFS_WRITE? 'A' : '-';
-       msg[2] = perms & VSS_READ? 'v' : '-';
-       msg[3] = perms & VSS_WRITE? 'V' : '-';
-       msg[4] = '\0';
-       return msg;
-}
-
 /*
  * Never returns NULL.
  */
@@ -123,7 +93,7 @@ static char *vss_get_status_flags(unsigned int flags)
        return msg;
 }
 
-static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly,
+static unsigned get_status(struct misc_meta_data *nmmd, bool parser_friendly,
                char **result)
 {
        char *status, *flags; /* vss status info */
@@ -138,18 +108,18 @@ static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly,
        clock_get_realtime(&current_time);
        /*
         * The calls to WRITE_STATUS_ITEM() below never fail because
-        * b->max_size is zero (unlimited), see para_printf(). However, clang
-        * is not smart enough to prove this and complains nevertheless.
+        * b->max_size is zero (unlimited), see \ref para_printf(). However,
+        * clang is not smart enough to prove this and complains nevertheless.
         * Casting the return value to void silences clang.
         */
-       (void)WRITE_STATUS_ITEM(&b, SI_STATUS, "%s\n", status);
-       (void)WRITE_STATUS_ITEM(&b, SI_STATUS_FLAGS, "%s\n", flags);
-       (void)WRITE_STATUS_ITEM(&b, SI_OFFSET, "%li\n", offset);
-       (void)WRITE_STATUS_ITEM(&b, SI_AFS_MODE, "%s\n", mmd->afs_mode_string);
-       (void)WRITE_STATUS_ITEM(&b, SI_STREAM_START, "%lu.%lu\n",
+       (void)WRITE_STATUS_ITEM(&b, SI_status, "%s\n", status);
+       (void)WRITE_STATUS_ITEM(&b, SI_status_flags, "%s\n", flags);
+       (void)WRITE_STATUS_ITEM(&b, SI_offset, "%li\n", offset);
+       (void)WRITE_STATUS_ITEM(&b, SI_afs_mode, "%s\n", mmd->afs_mode_string);
+       (void)WRITE_STATUS_ITEM(&b, SI_stream_start, "%lu.%lu\n",
                (long unsigned)nmmd->stream_start.tv_sec,
                (long unsigned)nmmd->stream_start.tv_usec);
-       (void)WRITE_STATUS_ITEM(&b, SI_CURRENT_TIME, "%lu.%lu\n",
+       (void)WRITE_STATUS_ITEM(&b, SI_current_time, "%lu.%lu\n",
                (long unsigned)current_time.tv_sec,
                (long unsigned)current_time.tv_usec);
        free(flags);
@@ -158,52 +128,6 @@ static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly,
        return b.offset;
 }
 
-static int check_sender_args(int argc, char * const * argv, struct sender_command_data *scd)
-{
-       int i;
-
-       const char *subcmds[] = {SENDER_SUBCOMMANDS NULL};
-       scd->sender_num = -1;
-       if (argc < 3)
-               return -E_COMMAND_SYNTAX;
-       for (i = 0; senders[i].name; i++)
-               if (!strcmp(senders[i].name, argv[1]))
-                       break;
-       PARA_DEBUG_LOG("%d:%s\n", argc, argv[1]);
-       if (!senders[i].name)
-               return -E_COMMAND_SYNTAX;
-       scd->sender_num = i;
-       for (i = 0; subcmds[i]; i++)
-               if (!strcmp(subcmds[i], argv[2]))
-                       break;
-       if (!subcmds[i])
-               return -E_COMMAND_SYNTAX;
-       scd->cmd_num = i;
-       if (!senders[scd->sender_num].client_cmds[scd->cmd_num])
-               return -E_SENDER_CMD;
-       switch (scd->cmd_num) {
-       case SENDER_on:
-       case SENDER_off:
-               if (argc != 3)
-                       return -E_COMMAND_SYNTAX;
-               break;
-       case SENDER_deny:
-       case SENDER_allow:
-               if (argc != 4 || parse_cidr(argv[3], scd->host,
-                               sizeof(scd->host), &scd->netmask) == NULL)
-                       return -E_COMMAND_SYNTAX;
-               break;
-       case SENDER_add:
-       case SENDER_delete:
-               if (argc != 4)
-                       return -E_COMMAND_SYNTAX;
-               return parse_fec_url(argv[3], scd);
-       default:
-               return -E_COMMAND_SYNTAX;
-       }
-       return 1;
-}
-
 /**
  * Send a sideband packet through a blocking file descriptor.
  *
@@ -280,7 +204,82 @@ int send_strerror(struct command_context *cc, int err)
 }
 
 /**
- * Send a sideband packet through a blocking file descriptor.
+ * Send an error context to a client,
+ *
+ * \param cc Client info.
+ * \param errctx The error context string.
+ *
+ * \return The return value of the underlying call to send_sb_va().
+ *
+ * This function frees the error context string after it was sent.
+ */
+int send_errctx(struct command_context *cc, char *errctx)
+{
+       int ret;
+
+       if (!errctx)
+               return 0;
+       ret = send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s\n", errctx);
+       free(errctx);
+       return ret;
+}
+
+static int check_sender_args(struct command_context *cc,
+               struct lls_parse_result *lpr, struct sender_command_data *scd)
+{
+       int i, ret;
+       const char * const subcmds[] = {SENDER_SUBCOMMANDS};
+       const char *arg;
+       char *errctx;
+       unsigned num_inputs = lls_num_inputs(lpr);
+
+       scd->sender_num = -1;
+       ret = lls(lls_check_arg_count(lpr, 2, INT_MAX, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       arg = lls_input(0, lpr);
+       FOR_EACH_SENDER(i)
+               if (strcmp(senders[i]->name, arg) == 0)
+                       break;
+       if (!senders[i])
+               return -E_COMMAND_SYNTAX;
+       scd->sender_num = i;
+       arg = lls_input(1, lpr);
+       for (i = 0; i < NUM_SENDER_CMDS; i++)
+               if (!strcmp(subcmds[i], arg))
+                       break;
+       if (i == NUM_SENDER_CMDS)
+               return -E_COMMAND_SYNTAX;
+       scd->cmd_num = i;
+       if (!senders[scd->sender_num]->client_cmds[scd->cmd_num])
+               return -E_SENDER_CMD;
+       switch (scd->cmd_num) {
+       case SENDER_on:
+       case SENDER_off:
+               if (num_inputs != 2)
+                       return -E_COMMAND_SYNTAX;
+               break;
+       case SENDER_deny:
+       case SENDER_allow:
+               if (num_inputs != 3 || parse_cidr(lls_input(2, lpr), scd->host,
+                               sizeof(scd->host), &scd->netmask) == NULL)
+                       return -E_COMMAND_SYNTAX;
+               break;
+       case SENDER_add:
+       case SENDER_delete:
+               if (num_inputs != 3)
+                       return -E_COMMAND_SYNTAX;
+               return parse_fec_url(lls_input(2, lpr), scd);
+       default:
+               return -E_COMMAND_SYNTAX;
+       }
+       return 1;
+}
+
+/**
+ * Receive a sideband packet from a blocking file descriptor.
  *
  * \param scc fd and crypto keys.
  * \param expected_band The expected band designator.
@@ -329,38 +328,39 @@ fail:
        return ret;
 }
 
-static int com_sender(struct command_context *cc)
+static int com_sender(struct command_context *cc, struct lls_parse_result *lpr)
 {
        int i, ret = 0;
        char *msg = NULL;
        struct sender_command_data scd;
 
-       if (cc->argc < 2) {
-               for (i = 0; senders[i].name; i++) {
+       if (lls_num_inputs(lpr) == 0) {
+               FOR_EACH_SENDER(i) {
                        char *tmp;
                        ret = xasprintf(&tmp, "%s%s\n", msg? msg : "",
-                               senders[i].name);
+                               senders[i]->name);
                        free(msg);
                        msg = tmp;
                }
                return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false);
        }
-       ret = check_sender_args(cc->argc, cc->argv, &scd);
+       ret = check_sender_args(cc, lpr, &scd);
        if (ret < 0) {
                if (scd.sender_num < 0)
                        return ret;
-               if (strcmp(cc->argv[2], "status") == 0)
-                       msg = senders[scd.sender_num].status();
+               if (strcmp(lls_input(1, lpr), "status") == 0)
+                       msg = senders[scd.sender_num]->status();
                else
-                       msg = senders[scd.sender_num].help();
+                       msg = senders[scd.sender_num]->help();
                return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
        }
 
        switch (scd.cmd_num) {
        case SENDER_add:
        case SENDER_delete:
-               assert(senders[scd.sender_num].resolve_target);
-               ret = senders[scd.sender_num].resolve_target(cc->argv[3], &scd);
+               assert(senders[scd.sender_num]->resolve_target);
+               ret = senders[scd.sender_num]->resolve_target(lls_input(2, lpr),
+                       &scd);
                if (ret < 0)
                        return ret;
        }
@@ -380,17 +380,16 @@ static int com_sender(struct command_context *cc)
        }
        return (i < 10)? 1 : -E_LOCK;
 }
+EXPORT_SERVER_CMD_HANDLER(sender);
 
-/* server info */
-static int com_si(struct command_context *cc)
+static int com_si(struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
 {
-       int ret;
        char *msg, *ut;
+       int ret;
 
-       if (cc->argc != 1)
-               return -E_COMMAND_SYNTAX;
-       mutex_lock(mmd_mutex);
        ut = daemon_get_uptime_str(now);
+       mutex_lock(mmd_mutex);
        ret = xasprintf(&msg,
                "up: %s\nplayed: %u\n"
                "server_pid: %d\n"
@@ -400,70 +399,71 @@ static int com_si(struct command_context *cc)
                "supported audio formats: %s\n",
                ut, mmd->num_played,
                (int)getppid(),
-               (int)mmd->afs_pid,
+               (int)afs_pid,
                mmd->active_connections,
                mmd->num_commands,
                mmd->num_connects,
-               conf.loglevel_arg,
+               ENUM_STRING_VAL(LOGLEVEL),
                AUDIO_FORMAT_HANDLERS
        );
        mutex_unlock(mmd_mutex);
        free(ut);
        return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false);
 }
+EXPORT_SERVER_CMD_HANDLER(si);
 
-/* version */
-static int com_version(struct command_context *cc)
+static int com_version(struct command_context *cc, struct lls_parse_result *lpr)
 {
        char *msg;
        size_t len;
 
-       if (cc->argc > 1 && strcmp(cc->argv[1], "-v") == 0)
+       if (SERVER_CMD_OPT_GIVEN(VERSION, VERBOSE, lpr))
                len = xasprintf(&msg, "%s", version_text("server"));
        else
                len = xasprintf(&msg, "%s\n", version_single_line("server"));
        return send_sb(&cc->scc, msg, len, SBD_OUTPUT, false);
 }
+EXPORT_SERVER_CMD_HANDLER(version);
 
 /** These status items are cleared if no audio file is currently open. */
 #define EMPTY_STATUS_ITEMS \
-       ITEM(PATH) \
-       ITEM(DIRECTORY) \
-       ITEM(BASENAME) \
-       ITEM(SCORE) \
-       ITEM(ATTRIBUTES_BITMAP) \
-       ITEM(ATTRIBUTES_TXT) \
-       ITEM(HASH) \
-       ITEM(IMAGE_ID) \
-       ITEM(IMAGE_NAME) \
-       ITEM(LYRICS_ID) \
-       ITEM(LYRICS_NAME) \
-       ITEM(BITRATE) \
-       ITEM(FORMAT) \
-       ITEM(FREQUENCY) \
-       ITEM(CHANNELS) \
-       ITEM(DURATION) \
-       ITEM(SECONDS_TOTAL) \
-       ITEM(NUM_PLAYED) \
-       ITEM(LAST_PLAYED) \
-       ITEM(TECHINFO) \
-       ITEM(ARTIST) \
-       ITEM(TITLE) \
-       ITEM(YEAR) \
-       ITEM(ALBUM) \
-       ITEM(COMMENT) \
-       ITEM(MTIME) \
-       ITEM(FILE_SIZE) \
-       ITEM(CHUNK_TIME) \
-       ITEM(NUM_CHUNKS) \
-       ITEM(AMPLIFICATION) \
+       ITEM(path) \
+       ITEM(directory) \
+       ITEM(basename) \
+       ITEM(score) \
+       ITEM(attributes_bitmap) \
+       ITEM(attributes_txt) \
+       ITEM(hash) \
+       ITEM(image_id) \
+       ITEM(image_name) \
+       ITEM(lyrics_id) \
+       ITEM(lyrics_name) \
+       ITEM(bitrate) \
+       ITEM(format) \
+       ITEM(frequency) \
+       ITEM(channels) \
+       ITEM(duration) \
+       ITEM(seconds_total) \
+       ITEM(num_played) \
+       ITEM(last_played) \
+       ITEM(techinfo) \
+       ITEM(artist) \
+       ITEM(title) \
+       ITEM(year) \
+       ITEM(album) \
+       ITEM(comment) \
+       ITEM(mtime) \
+       ITEM(file_size) \
+       ITEM(chunk_time) \
+       ITEM(num_chunks) \
+       ITEM(amplification) \
+       ITEM(play_time) \
 
-/**
- * Write a list of audio-file related status items with empty values.
- *
- * This is used by vss when currently no audio file is open.
+/*
+ * Create a set of audio-file related status items with empty values. These are
+ * written to stat clients when no audio file is open.
  */
-static unsigned empty_status_items(int parser_friendly, char **result)
+static unsigned empty_status_items(bool parser_friendly, char **result)
 {
        char *esi;
        unsigned len;
@@ -491,40 +491,29 @@ static unsigned empty_status_items(int parser_friendly, char **result)
 }
 #undef EMPTY_STATUS_ITEMS
 
-/* stat */
-static int com_stat(struct command_context *cc)
+static int com_stat(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       int i, ret;
+       int ret;
        struct misc_meta_data tmp, *nmmd = &tmp;
        char *s;
-       int32_t num = 0;
-       int parser_friendly = 0;
-
-       para_sigaction(SIGUSR1, dummy);
-
-       for (i = 1; i < cc->argc; i++) {
-               const char *arg = cc->argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strncmp(arg, "-n=", 3)) {
-                       ret = para_atoi32(arg + 3, &num);
-                       if (ret < 0)
-                               return ret;
-                       continue;
-               }
-               if (!strcmp(arg, "-p")) {
-                       parser_friendly = 1;
-                       continue;
-               }
-               return -E_COMMAND_SYNTAX;
-       }
-       if (i != cc->argc)
-               return -E_COMMAND_SYNTAX;
+       bool parser_friendly = SERVER_CMD_OPT_GIVEN(STAT, PARSER_FRIENDLY,
+               lpr) > 0;
+       uint32_t num = SERVER_CMD_UINT32_VAL(STAT, NUM, lpr);
+       const struct timespec ts = {.tv_sec = 50, .tv_nsec = 0};
+
+       para_sigaction(SIGINT, SIG_IGN);
+       para_sigaction(SIGUSR1, command_handler_sighandler);
+       para_sigaction(SIGTERM, command_handler_sighandler);
+       /*
+        * Simply checking subcmd_should_die is racy because a signal may
+        * arrive after the check but before the subsequent call to sleep(3).
+        * If this happens, sleep(3) would not be interrupted by the signal.
+        * To avoid this we block SIGTERM here and allow it to arrive only
+        * while we sleep.
+        */
+       para_block_signal(SIGTERM);
        for (;;) {
+               sigset_t set;
                /*
                 * Copy the mmd structure to minimize the time we hold the mmd
                 * lock.
@@ -547,7 +536,15 @@ static int com_stat(struct command_context *cc)
                ret = 1;
                if (num > 0 && !--num)
                        goto out;
-               sleep(50);
+               sigemptyset(&set); /* empty set means: unblock all signals */
+               /*
+                * pselect(2) allows to atomically unblock signals, then go to
+                * sleep. Calling sigprocmask(2) followed by sleep(3) would
+                * open a race window similar to the one described above.
+                */
+               pselect(1, NULL, NULL, NULL, &ts, &set);
+               if (subcmd_should_die)
+                       goto out;
                ret = -E_SERVER_CRASH;
                if (getppid() == 1)
                        goto out;
@@ -555,114 +552,70 @@ static int com_stat(struct command_context *cc)
 out:
        return ret;
 }
+EXPORT_SERVER_CMD_HANDLER(stat);
 
-static int send_list_of_commands(struct command_context *cc, struct server_command *cmd,
-               const char *handler)
+static const char *aux_info_cb(unsigned cmd_num, bool verbose)
 {
-       char *msg = NULL;
+       static char result[80];
+       unsigned perms = server_command_perms[cmd_num];
 
-       for (; cmd->name; cmd++) {
-               char *tmp, *perms = cmd_perms_itohuman(cmd->perms);
-               tmp = make_message("%s\t%s\t%s\t%s\n", cmd->name, handler,
-                       perms, cmd->description);
-               free(perms);
-               msg = para_strcat(msg, tmp);
-               free(tmp);
+       if (verbose) {
+               /* permissions: VSS_READ | VSS_WRITE */
+               sprintf(result, "permissions: %s",
+                       server_command_perms_txt[cmd_num]);
+       } else {
+               result[0] = perms & AFS_READ? 'a' : '-';
+               result[1] = perms & AFS_WRITE? 'A' : '-';
+               result[2] = perms & VSS_READ? 'v' : '-';
+               result[3] = perms & VSS_WRITE? 'V' : '-';
+               result[4] = '\0';
        }
-       assert(msg);
-       return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
-}
-
-/* returns string that must be freed by the caller */
-static struct server_command *get_cmd_ptr(const char *name, char **handler)
-{
-       struct server_command *cmd;
-
-       for (cmd = server_cmds; cmd->name; cmd++)
-               if (!strcmp(cmd->name, name)) {
-                       if (handler)
-                               *handler = para_strdup("server"); /* server commands */
-                       return cmd;
-               }
-       /* not found, look for commands supported by afs */
-       for (cmd = afs_cmds; cmd->name; cmd++)
-               if (!strcmp(cmd->name, name)) {
-                       if (handler)
-                               *handler = para_strdup("afs");
-                       return cmd;
-               }
-       return NULL;
+       return result;
 }
 
-/* help */
-static int com_help(struct command_context *cc)
+static int com_help(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       struct server_command *cmd;
-       char *perms, *handler, *buf;
+       char *buf;
        int ret;
+       unsigned n;
+       bool long_help = SERVER_CMD_OPT_GIVEN(HELP, LONG, lpr);
 
-       if (cc->argc < 2) {
-               /* no argument given, print list of commands */
-               if ((ret = send_list_of_commands(cc, server_cmds, "server")) < 0)
-                       return ret;
-               return send_list_of_commands(cc, afs_cmds, "afs");
-       }
-       /* argument given for help */
-       cmd = get_cmd_ptr(cc->argv[1], &handler);
-       if (!cmd)
-               return -E_BAD_CMD;
-       perms = cmd_perms_itohuman(cmd->perms);
-       ret = xasprintf(&buf, "%s - %s\n\n"
-               "handler: %s\n"
-               "permissions: %s\n"
-               "usage: %s\n\n"
-               "%s\n",
-               cc->argv[1],
-               cmd->description,
-               handler,
-               perms,
-               cmd->usage,
-               cmd->help
-       );
-       free(perms);
-       free(handler);
-       return send_sb(&cc->scc, buf, ret, SBD_OUTPUT, false);
+       lsu_com_help(long_help, lpr, server_cmd_suite, aux_info_cb, &buf, &n);
+       ret = send_sb(&cc->scc, buf, n, SBD_OUTPUT, false);
+       return ret;
 }
+EXPORT_SERVER_CMD_HANDLER(help);
 
-/* hup */
-static int com_hup(struct command_context *cc)
+static int com_hup(__a_unused struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
 {
-       if (cc->argc != 1)
-               return -E_COMMAND_SYNTAX;
        kill(getppid(), SIGHUP);
        return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(hup);
 
-/* term */
-static int com_term(struct command_context *cc)
+static int com_term(__a_unused struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
 {
-       if (cc->argc != 1)
-               return -E_COMMAND_SYNTAX;
        kill(getppid(), SIGTERM);
        return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(term);
 
-static int com_play(struct command_context *cc)
+static int com_play(__a_unused struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
 {
-       if (cc->argc != 1)
-               return -E_COMMAND_SYNTAX;
        mutex_lock(mmd_mutex);
        mmd->new_vss_status_flags |= VSS_PLAYING;
        mmd->new_vss_status_flags &= ~VSS_NOMORE;
        mutex_unlock(mmd_mutex);
        return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(play);
 
-/* stop */
-static int com_stop(struct command_context *cc)
+static int com_stop(__a_unused struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
 {
-       if (cc->argc != 1)
-               return -E_COMMAND_SYNTAX;
        mutex_lock(mmd_mutex);
        mmd->new_vss_status_flags &= ~VSS_PLAYING;
        mmd->new_vss_status_flags &= ~VSS_REPOS;
@@ -670,12 +623,11 @@ static int com_stop(struct command_context *cc)
        mutex_unlock(mmd_mutex);
        return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(stop);
 
-/* pause */
-static int com_pause(struct command_context *cc)
+static int com_pause(__a_unused struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
 {
-       if (cc->argc != 1)
-               return -E_COMMAND_SYNTAX;
        mutex_lock(mmd_mutex);
        if (!vss_paused() && !vss_stopped()) {
                mmd->events++;
@@ -685,55 +637,68 @@ static int com_pause(struct command_context *cc)
        mutex_unlock(mmd_mutex);
        return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(pause);
 
-/* next */
-static int com_next(struct command_context *cc)
+static int com_next(__a_unused struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
 {
-       if (cc->argc != 1)
-               return -E_COMMAND_SYNTAX;
        mutex_lock(mmd_mutex);
        mmd->events++;
        mmd->new_vss_status_flags |= VSS_NEXT;
        mutex_unlock(mmd_mutex);
        return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(next);
 
-/* nomore */
-static int com_nomore(struct command_context *cc)
+static int com_nomore(__a_unused struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
 {
-       if (cc->argc != 1)
-               return -E_COMMAND_SYNTAX;
        mutex_lock(mmd_mutex);
        if (vss_playing() || vss_paused())
                mmd->new_vss_status_flags |= VSS_NOMORE;
        mutex_unlock(mmd_mutex);
        return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(nomore);
 
-/* ff */
-static int com_ff(struct command_context *cc)
+static int com_ff(struct command_context *cc, struct lls_parse_result *lpr)
 {
        long promille;
-       int ret, backwards = 0;
-       unsigned i;
-       char c;
+       int i, ret;
+       char c, *errctx;
 
-       if (cc->argc != 2)
-               return -E_COMMAND_SYNTAX;
-       if (!(ret = sscanf(cc->argv[1], "%u%c", &i, &c)))
-               return -E_COMMAND_SYNTAX;
-       if (ret > 1 && c == '-')
-               backwards = 1; /* jmp backwards */
+       ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               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;
+               }
+       }
        mutex_lock(mmd_mutex);
        ret = -E_NO_AUDIO_FILE;
        if (!mmd->afd.afhi.chunks_total || !mmd->afd.afhi.seconds_total)
                goto out;
        ret = 1;
        promille = (1000 * mmd->current_chunk) / mmd->afd.afhi.chunks_total;
-       if (backwards)
-               promille -= 1000 * i / mmd->afd.afhi.seconds_total;
-       else
-               promille += 1000 * i / mmd->afd.afhi.seconds_total;
+       /*
+        * We need this cast because without it the expression on the right
+        * hand side is of unsigned type.
+        */
+       promille += 1000 * i / (int)mmd->afd.afhi.seconds_total;
        if (promille < 0)
                promille = 0;
        if (promille > 1000) {
@@ -748,27 +713,28 @@ out:
        mutex_unlock(mmd_mutex);
        return ret;
 }
+EXPORT_SERVER_CMD_HANDLER(ff);
 
-/* jmp */
-static int com_jmp(struct command_context *cc)
+static int com_jmp(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       long unsigned int i;
-       int ret;
+       int i, ret;
+       char *errctx;
 
-       if (cc->argc != 2)
-               return -E_COMMAND_SYNTAX;
-       if (sscanf(cc->argv[1], "%lu", &i) <= 0)
-               return -E_COMMAND_SYNTAX;
+       ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       if (sscanf(lls_input(0, lpr), "%d", &i) <= 0)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       if (i < 0 || i > 100)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
        mutex_lock(mmd_mutex);
        ret = -E_NO_AUDIO_FILE;
        if (!mmd->afd.afhi.chunks_total)
                goto out;
-       if (i > 100)
-               i = 100;
-       PARA_INFO_LOG("jumping to %lu%%\n", i);
+       PARA_INFO_LOG("jumping to %d%%\n", i);
        mmd->repos_request = (mmd->afd.afhi.chunks_total * i + 50) / 100;
-       PARA_INFO_LOG("sent: %lu, offset before jmp: %li\n",
-               mmd->chunks_sent, mmd->offset);
        mmd->new_vss_status_flags |= VSS_REPOS;
        mmd->new_vss_status_flags &= ~VSS_NEXT;
        ret = 1;
@@ -777,26 +743,15 @@ out:
        mutex_unlock(mmd_mutex);
        return ret;
 }
+EXPORT_SERVER_CMD_HANDLER(jmp);
 
-static int com_tasks(struct command_context *cc)
-{
-       char *tl = server_get_tasks();
-       int ret = 1;
-
-       if (tl)
-               ret = send_sb(&cc->scc, tl, strlen(tl), SBD_OUTPUT, false);
-       return ret;
-}
-
-/*
- * check if perms are sufficient to exec a command having perms cmd_perms.
- * Returns 0 if perms are sufficient, -E_PERM otherwise.
- */
-static int check_perms(unsigned int perms, const struct server_command *cmd_ptr)
+/* deprecated, does nothing */
+static int com_tasks(__a_unused struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
 {
-       PARA_DEBUG_LOG("checking permissions\n");
-       return (cmd_ptr->perms & perms) < cmd_ptr->perms ? -E_PERM : 0;
+       return 1;
 }
+EXPORT_SERVER_CMD_HANDLER(tasks);
 
 static void reset_signals(void)
 {
@@ -807,10 +762,10 @@ static void reset_signals(void)
 }
 
 struct connection_features {
-       bool aes_ctr128_requested;
+       int dummy; /* none at the moment */
 };
 
-static int parse_auth_request(char *buf, int len, struct user **u,
+static int parse_auth_request(char *buf, int len, const struct user **u,
                struct connection_features *cf)
 {
        int ret;
@@ -836,7 +791,7 @@ static int parse_auth_request(char *buf, int len, struct user **u,
                        if (strcmp(features[i], "sideband") == 0)
                                continue;
                        if (strcmp(features[i], "aes_ctr128") == 0)
-                               cf->aes_ctr128_requested = true;
+                               continue;
                        else {
                                ret = -E_BAD_FEATURE;
                                goto out;
@@ -844,7 +799,7 @@ static int parse_auth_request(char *buf, int len, struct user **u,
                }
        }
        PARA_DEBUG_LOG("received auth request for user %s\n", username);
-       *u = lookup_user(username);
+       *u = user_list_lookup(username);
        ret = 1;
 out:
        free_argv(features);
@@ -853,40 +808,52 @@ out:
 
 #define HANDSHAKE_BUFSIZE 4096
 
-static int run_command(struct command_context *cc, struct iovec *iov,
-               const char *peername)
+static int run_command(struct command_context *cc, struct iovec *iov)
 {
-       int ret, i;
-       char *p, *end;
-       struct server_command *cmd;
+       int ret, i, argc;
+       char *p, *end, **argv;
+       const struct lls_command *lcmd = NULL;
+       unsigned perms;
+       struct lls_parse_result *lpr;
+       char *errctx;
 
        if (iov->iov_base == NULL || iov->iov_len == 0)
-               return -E_BAD_CMD;
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
        p = iov->iov_base;
        p[iov->iov_len - 1] = '\0'; /* just to be sure */
-       cmd = get_cmd_ptr(p, NULL);
-       if (!cmd)
-               return -E_BAD_CMD;
-       ret = check_perms(cc->u->perms, cmd);
-       if (ret < 0)
+
+       ret = lls(lls_lookup_subcmd(p, server_cmd_suite, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
                return ret;
+       }
+       perms = server_command_perms[ret];
+       if ((perms & cc->u->perms) != perms)
+               return -E_PERM;
+       lcmd = lls_cmd(ret, server_cmd_suite);
        end = iov->iov_base + iov->iov_len;
        for (i = 0; p < end; i++)
                p += strlen(p) + 1;
-       cc->argc = i;
-       cc->argv = para_malloc((cc->argc + 1) * sizeof(char *));
+       argc = i;
+       argv = para_malloc((argc + 1) * sizeof(char *));
        for (i = 0, p = iov->iov_base; p < end; i++) {
-               cc->argv[i] = para_strdup(p);
+               argv[i] = para_strdup(p);
                p += strlen(p) + 1;
        }
-       cc->argv[cc->argc] = NULL;
-       PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cmd->name,
-               cc->u->name, peername);
-       ret = cmd->handler(cc);
-       free_argv(cc->argv);
+       argv[argc] = NULL;
+       PARA_NOTICE_LOG("calling com_%s() for user %s\n",
+               lls_command_name(lcmd), cc->u->name);
+       ret = lls(lls_parse(argc, argv, lcmd, &lpr, &errctx));
+       if (ret >= 0) {
+               const struct server_cmd_user_data *ud = lls_user_data(lcmd);
+               ret = ud->handler(cc, lpr);
+               lls_free_parse_result(lpr, lcmd);
+       } else
+               send_errctx(cc, errctx);
+       free_argv(argv);
        mutex_lock(mmd_mutex);
        mmd->num_commands++;
-       if (ret >= 0 && (cmd->perms & AFS_WRITE))
+       if (ret >= 0 && (perms & AFS_WRITE))
                mmd->events++;
        mutex_unlock(mmd_mutex);
        return ret;
@@ -896,35 +863,36 @@ static int run_command(struct command_context *cc, struct iovec *iov,
  * Perform user authentication and execute a command.
  *
  * \param fd The file descriptor to send output to.
- * \param peername Identifies the connecting peer.
  *
  * Whenever para_server accepts an incoming tcp connection on the port it
  * listens on, it forks and the resulting child calls this function.
  *
- * An RSA-based challenge/response is used to authenticate the peer. It that
+ * An RSA-based challenge/response is used to authenticate the peer. If the
  * authentication succeeds, a random session key is generated and sent back to
  * the peer, encrypted with its RSA public key. From this point on, all
- * transfers are crypted with this session key.
+ * transfers are encrypted with this session key using a stream cipher.
  *
  * Next it is checked if the peer supplied a valid server command or a command
  * for the audio file selector. If yes, and if the user has sufficient
- * permissions to execute that command, the function calls the corresponding
- * command handler which does argument checking and further processing.
+ * permissions to execute this command, the function calls the corresponding
+ * command handler which performs argument checking and further processing.
  *
- * In order to cope with a DOS attacks, a timeout is set up which terminates
- * the function if the connection was not authenticated when the timeout
- * expires.
+ * To cope with DOS attacks, a timer is set up right after the fork. If the
+ * connection was still not authenticated when the timeout expires, the child
+ * process is terminated.
  *
- * \sa alarm(2), crypt.c, crypt.h
+ * \return Standard.
+ *
+ * \sa alarm(2), \ref openssl.c, \ref crypt.h.
  */
-__noreturn void handle_connect(int fd, const char *peername)
+int handle_connect(int fd)
 {
        int ret;
-       unsigned char rand_buf[CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
+       unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
        unsigned char challenge_hash[HASH_SIZE];
        char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
        size_t numbytes;
-       struct command_context cc_struct = {.peer = peername}, *cc = &cc_struct;
+       struct command_context cc_struct = {.u = NULL}, *cc = &cc_struct;
        struct iovec iov;
        struct connection_features cf;
 
@@ -950,7 +918,7 @@ __noreturn void handle_connect(int fd, const char *peername)
                goto net_err;
        if (cc->u) {
                get_random_bytes_or_die(rand_buf, sizeof(rand_buf));
-               ret = pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf),
+               ret = apc_pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf),
                        (unsigned char *)buf);
                if (ret < 0)
                        goto net_err;
@@ -965,7 +933,7 @@ __noreturn void handle_connect(int fd, const char *peername)
                get_random_bytes_or_die((unsigned char *)buf, numbytes);
        }
        PARA_DEBUG_LOG("sending %d byte challenge + session key (%zu bytes)\n",
-               CHALLENGE_SIZE, numbytes);
+               APC_CHALLENGE_SIZE, numbytes);
        ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false);
        buf = NULL;
        if (ret < 0)
@@ -981,30 +949,29 @@ __noreturn void handle_connect(int fd, const char *peername)
        if (!cc->u)
                goto net_err;
        /*
-        * The correct response is the hash of the first CHALLENGE_SIZE bytes
+        * The correct response is the hash of the first APC_CHALLENGE_SIZE bytes
         * of the random data.
         */
        ret = -E_BAD_AUTH;
        if (numbytes != HASH_SIZE)
                goto net_err;
-       hash_function((char *)rand_buf, CHALLENGE_SIZE, challenge_hash);
+       hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
        if (memcmp(challenge_hash, buf, HASH_SIZE))
                goto net_err;
        /* auth successful */
        alarm(0);
        PARA_INFO_LOG("good auth for %s\n", cc->u->name);
        /* init stream cipher keys with the second part of the random buffer */
-       cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN,
-               cf.aes_ctr128_requested);
-       cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
-               SESSION_KEY_LEN, cf.aes_ctr128_requested);
+       cc->scc.recv = sc_new(rand_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN);
+       cc->scc.send = sc_new(rand_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN,
+               SESSION_KEY_LEN);
        ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false);
        if (ret < 0)
                goto net_err;
        ret = recv_sb(&cc->scc, SBD_COMMAND, MAX_COMMAND_LEN, &iov);
        if (ret < 0)
                goto net_err;
-       ret = run_command(cc, &iov, peername);
+       ret = run_command(cc, &iov);
        free(iov.iov_base);
        if (ret < 0)
                goto err_out;
@@ -1028,5 +995,5 @@ out:
        }
        sc_free(cc->scc.recv);
        sc_free(cc->scc.send);
-       exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
+       return ret;
 }
index 5e1a4ce3edc1a8812948ada45eecd2f76df4f7be..a7fa4673c696fe52f79bcb17dd4ca2a10ae720f9 100644 (file)
--- a/command.h
+++ b/command.h
@@ -2,23 +2,57 @@
 
 /** Per connection data available to command handlers. */
 struct command_context {
-       /** Network address of the peer. */
-       const char *peer;
        /** The paraslash user that executes this command. */
-       struct user *u;
-       /** Argument count. */
-       int argc;
-       /** Argument vector. */
-       char **argv;
+       const struct user *u;
        /** File descriptor and crypto keys. */
        struct stream_cipher_context scc;
 };
 
+/** Prototype of a server command handler. */
+typedef int (*server_cmd_handler_t)(struct command_context *,
+               struct lls_parse_result *);
+
+/**
+ * The lopsub user data structure for server commands.
+ *
+ * One such structure exists for each server command. Lopsub maintains
+ * references to the user data structures and provides lls_user_data() for
+ * applications to fetch the user data pointer of a given command. This
+ * mechanism is used by the dispatcher of command.c to run the command handler.
+ */
+struct server_cmd_user_data {
+       /** Pointer to the command handler. */
+       server_cmd_handler_t handler;
+};
+
+/** Define the user data structure for one command. */
+#define EXPORT_SERVER_CMD_HANDLER(_cmd) \
+       const struct server_cmd_user_data lsg_server_cmd_com_ ## _cmd ## _user_data = { \
+               .handler = com_ ## _cmd \
+       };
+
+/** Get the lopsub command pointer by command name. */
+#define SERVER_CMD_CMD_PTR(_cmd) \
+       (lls_cmd(LSG_SERVER_CMD_CMD_ ## _cmd, server_cmd_suite))
+
+/** Get the lopsub parse result of an option. */
+#define SERVER_CMD_OPT_RESULT(_cmd, _opt, _lpr) \
+               (lls_opt_result(LSG_SERVER_CMD_ ## _cmd ## _OPT_ ## _opt, _lpr))
+
+/** How many times an option was given. */
+#define SERVER_CMD_OPT_GIVEN(_cmd, _opt, _lpr) \
+       (lls_opt_given(SERVER_CMD_OPT_RESULT(_cmd, _opt, _lpr)))
+
+/** Fetch the (first) argument given to an option of type uint32. */
+#define SERVER_CMD_UINT32_VAL(_cmd, _opt, _lpr) \
+       (lls_uint32_val(0, SERVER_CMD_OPT_RESULT(_cmd, _opt, _lpr)))
+
 int send_sb(struct stream_cipher_context *scc, void *buf, size_t numbytes,
                int band, bool dont_free);
 __printf_3_4 int send_sb_va(struct stream_cipher_context *scc, int band,
                const char *fmt, ...);
 int send_strerror(struct command_context *cc, int err);
+int send_errctx(struct command_context *cc, char *errctx);
 int recv_sb(struct stream_cipher_context *scc,
                enum sb_designator expected_band,
                size_t max_size, struct iovec *result);
diff --git a/command_util.bash b/command_util.bash
deleted file mode 100755 (executable)
index 5e9dbcc..0000000
+++ /dev/null
@@ -1,291 +0,0 @@
-#!/usr/bin/env bash
-
-read_header()
-{
-       local key value i
-
-       while read key value; do
-               case "$key" in
-               ---)
-                       break
-                       ;;
-               BN:)
-                       base_name="$value"
-                       ;;
-               SF:)
-                       source_files="$value"
-                       ;;
-               SN:)
-                       section_name="$value"
-                       ;;
-               TM:)
-                       template_members="$value"
-               esac
-       done
-}
-
-read_one_command()
-{
-       local line
-
-       name_txt=""
-       desc_txt=""
-       usage_txt=""
-       help_txt=""
-       perms_txt=""
-       template=0
-       template_name=""
-       template_prototype=""
-       while read key value; do
-               case "$key" in
-               ---)
-                       break
-                       ;;
-               N:)
-                       name_txt="$value"
-                       ;;
-               T:)
-                       template_name="$value"
-                       template=1
-                       ;;
-               O:)
-                       template_prototype="$value"
-                       template=1
-                       ;;
-               P:)
-                       perms_txt="$value"
-                       ;;
-               D:)
-                       desc_txt="$value"
-                       ;;
-               U:)
-                       usage_txt="$value"
-                       ;;
-               H:)
-                       help_txt="${value}"
-                       while read line; do
-                               if test "$line" = "---"; then
-                                       break;
-                               fi
-                               line=${line#H:}
-                               help_txt="$help_txt
-${line# }"
-                       done
-                       break
-                       ;;
-               esac
-       done
-       if test $template -eq 0; then
-               if test -n "$name_txt" -a -n "$desc_txt" -a -n "$usage_txt" \
-                               -a -n "$help_txt"; then
-                       ret=1
-                       return
-               fi
-       else
-               if test -n "$template_name" -a -n "$template_prototype" \
-                               -a -n "$name_txt " -a -n "$template_members" \
-                               -a -n "$desc_txt" -a -n "$usage_txt" \
-                               -a -n "$help_txt"; then
-                       ret=1
-                       return
-               fi
-       fi
-       if test -z "$name_txt" -a -z "$desc_txt" -a -z "$usage_txt" \
-                       -a -z "$help_txt"; then
-               ret=0
-               return
-       fi
-       ret=-1
-       #return
-       echo "!ERROR!"
-       echo "N: $name_txt"
-       echo "D: $desc_txt"
-       echo "S: $usage_txt"
-       echo "P: $perms_txt"
-       echo "H: $help_txt"
-       echo "O: $template_prototype"
-}
-
-dump_man()
-{
-       if test $template -eq 0; then
-               echo ".SS \"$name_txt\""
-               echo "$desc_txt"
-               echo
-               echo "\\fBUsage: \\fP$usage_txt"
-       else
-               for member in $template_members; do
-                       local sed_cmd="sed -e s/@member@/$member/g"
-                       local t_name_txt=$(echo $name_txt | $sed_cmd)
-                       echo ".SS \"$t_name_txt\""
-               done
-               echo "$desc_txt" | sed -e "s/@member@/{$(echo $template_members | sed -e 's/ / | /g')}/g"
-               echo
-               echo "\\fBUsage: \\fP"
-               echo
-               echo ".nf"
-               for member in $template_members; do
-                       local sed_cmd="sed -e s/@member@/$member/g"
-                       local t_usage_txt=$(echo $usage_txt | $sed_cmd)
-                       printf "\t$t_usage_txt\n"
-               done
-               echo ".fi"
-       fi
-       echo
-       echo "$help_txt" | sed -e 's/^  //'
-       echo
-       if test -n "$perms_txt"; then
-               echo -n "\\fBpermissions:\\fP "
-               if test "$perms_txt" = "0"; then
-                       echo "(none)"
-               else
-                       echo "$perms_txt"
-               fi
-       fi
-       echo
-}
-
-
-com_man()
-{
-       echo "[$section_name]"
-       echo
-       while : ; do
-               read_one_command
-               if test $ret -lt 0; then
-                       exit 1
-               fi
-               if test $ret -eq 0; then
-                       break
-               fi
-               dump_man
-       done
-}
-
-cmd_handler_name()
-{
-       result="com_$name_txt,"
-}
-
-make_array_member()
-{
-       local TAB='     ' CR='
-'
-       local tmp
-
-       result="{.name = \"$name_txt\", .handler = com_$name_txt, "
-       if test -n "$perms_txt"; then
-               result="$result .perms = $perms_txt,"
-       fi
-       result="$result.description = \"$desc_txt\", .usage = \"$usage_txt\", \\$CR .help = "
-       tmp="$(printf "%s\n" "$help_txt" | sed -e 's/^/\"/g' -e 's/$/\\n\"/g' \
-               -e "s/$TAB/\\\t/g" -e's/$/\\/g')"
-       result="$result$tmp$CR}, \\$CR"
-}
-
-make_completion()
-{
-       local CR='
-'
-       result="  {.name = \"$name_txt\", .completer = ${name_txt}_completer}, \\$CR"
-}
-
-template_loop()
-{
-       local loop_result=
-
-       local t_name="$name_txt"
-       local t_perms="$perms_txt"
-       local t_desc="$desc_txt"
-       local t_usage="$usage_txt"
-       local t_help="$help_txt"
-       local t_source_files="$source_files"
-       local member
-       for member in $template_members; do
-               name_txt="${t_name//@member@/$member}"
-               perms_txt="${t_perms//@member@/$member}"
-               desc_txt="${t_desc//@member@/$member}"
-               usage_txt="${t_usage//@member@/$member}"
-               help_txt="${t_help//@member@/$member}"
-               prototype="${template_prototype//@member@/$member}"
-               result=
-               $1
-               loop_result="$loop_result$result"
-       done
-       result="$loop_result"
-       # reset global variables
-       name_txt="$t_name"
-       perms_txt="$t_perms"
-       desc_txt="$t_desc"
-       usage_txt="$t_usage"
-       help_txt="$t_help"
-       source_files="$t_source_files"
-}
-
-com_header()
-{
-       local array_members handlers= CR='
-'
-
-       while : ; do
-               read_one_command
-               if test $ret -lt 0; then
-                       exit 1
-               fi
-               if test $ret -eq 0; then
-                       break
-               fi
-               if test $template -eq 0; then
-                       cmd_handler_name
-                       handlers+="$result"
-                       make_array_member
-                       array_members="$array_members$result"
-                       continue
-               fi
-               template_loop cmd_handler_name
-               handlers+="$result"
-               template_loop make_array_member
-               array_members="$array_members$result"
-       done
-       array_members="$array_members{.name = NULL} \\$CR"
-       echo "#define DEFINE_${base_name^^}_CMD_ARRAY $array_members"
-       echo "#define ${base_name^^}_COMMAND_HANDLERS ${handlers%,}"
-}
-
-com_completion()
-{
-
-       echo "#define $1 \\"
-       while : ; do
-               read_one_command
-               if test $ret -lt 0; then
-                       exit 1
-               fi
-               if test $ret -eq 0; then
-                       break
-               fi
-               if test $template -eq 0; then
-                       make_completion
-                       printf "%s" "$result"
-                       continue
-               fi
-               template_loop make_completion
-               printf "%s" "$result"
-       done
-       echo
-}
-
-read_header
-arg="$1"
-shift
-case "$arg" in
-       "h")
-               com_header
-               ;;
-       "man")
-               com_man $*
-               ;;
-       "compl")
-               com_completion $*
-               ;;
-esac
index d0d96fd960955bcccedc731eb33221d5369e778f..ff4ce6fb7663c5a0c35e1a42b433060b3b162b89 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file compress_filter.c Paraslash's dynamic audio range compressor. */
 
  */
 
 #include <regex.h>
+#include <lopsub.h>
 
+#include "filter_cmd.lsg.h"
 #include "para.h"
-#include "compress_filter.cmdline.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
 #include "error.h"
 
+#define U32_OPTVAL(_opt, _lpr) (FILTER_CMD_OPT_UINT32_VAL(COMPRESS, _opt, _lpr))
+
 /** Data specific to the compress filter. */
 struct private_compress_data {
        /** The current multiplier. */
        unsigned current_gain;
-       /** Points to the configuration data for this instance of the compress filter. */
-       struct compress_filter_args_info *conf;
        /** Maximal admissible gain. */
        unsigned max_gain;
        /** Number of samples already seen. */
@@ -51,9 +47,8 @@ static int compress_post_select(__a_unused struct sched *s, void *context)
        char *inbuf;
        size_t length, i;
        int16_t *ip, *op;
-       unsigned gain_shift = pcd->conf->inertia_arg + pcd->conf->damp_arg,
-               mask = (1 << pcd->conf->blocksize_arg) - 1;
-
+       uint32_t inertia = U32_OPTVAL(INERTIA, fn->lpr);
+       unsigned mask = (1U << U32_OPTVAL(BLOCKSIZE, fn->lpr)) - 1U;
        //inplace = false;
 next_buffer:
        ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
@@ -82,25 +77,28 @@ next_buffer:
                        neg = true;
                }
                sample *= pcd->current_gain;
-               sample >>= gain_shift;
+               sample >>= inertia + 1;
                if (sample > 32767) { /* clip */
+                       PARA_NOTICE_LOG("clip: %d\n", sample);
                        sample = 32767;
                        pcd->current_gain = (3 * pcd->current_gain +
-                               (1 << pcd->conf->inertia_arg)) / 4;
+                               (1 << inertia)) / 4;
                        pcd->peak = 0;
                } else if (sample > pcd->peak)
                        pcd->peak = sample;
+               sample >>= U32_OPTVAL(DAMP, fn->lpr);
                op[i] = neg? -sample : sample;
                if (++pcd->num_samples & mask)
                        continue;
 //             PARA_DEBUG_LOG("gain: %u, peak: %u\n", pcd->current_gain,
 //                     pcd->peak);
-               if (pcd->peak < pcd->conf->target_level_arg) {
+
+               if (pcd->peak < U32_OPTVAL(TARGET_LEVEL, fn->lpr)) {
                        if (pcd->current_gain < pcd->max_gain)
                                pcd->current_gain++;
                } else
                        pcd->current_gain = PARA_MAX(pcd->current_gain - 2,
-                               1U << pcd->conf->inertia_arg);
+                               1U << inertia);
                pcd->peak = 0;
        }
        if (inplace)
@@ -116,47 +114,54 @@ err:
        return ret;
 }
 
-/** TODO: Add sanity checks */
-static int compress_parse_config(int argc, char **argv, void **config)
-{
-       struct compress_filter_args_info *conf = para_calloc(sizeof(*conf));
-
-       compress_filter_cmdline_parser(argc, argv, conf);
-       *config = conf;
-       return 1;
-}
-
 static void compress_open(struct filter_node *fn)
 {
-       struct private_compress_data *pcd = para_calloc(
-               sizeof(struct private_compress_data));
-       pcd->conf = fn->conf;
+       struct private_compress_data *pcd = para_calloc(sizeof(*pcd));
+       uint32_t inertia = U32_OPTVAL(INERTIA, fn->lpr);
+       uint32_t aggressiveness = U32_OPTVAL(AGGRESSIVENESS, fn->lpr);
+
        fn->private_data = pcd;
        fn->min_iqs = 2; /* 16 bit audio */
-       pcd->current_gain = 1 << pcd->conf->inertia_arg;
-       pcd->max_gain = 1 << (pcd->conf->inertia_arg + pcd->conf->aggressiveness_arg);
+       pcd->current_gain = 1U << inertia;
+       pcd->max_gain = (1U << inertia) * (1.0 + 3.0 * aggressiveness / 10.0);
 }
 
-static void compress_free_config(void *conf)
+static void *compress_setup(const struct lls_parse_result *lpr)
 {
-       compress_filter_cmdline_parser_free(conf);
-}
+       uint32_t val;
 
-/**
- * The init function of the compress filter.
- *
- * \param f Pointer to the struct to initialize.
- */
-void compress_filter_init(struct filter *f)
-{
-       struct compress_filter_args_info dummy;
-
-       compress_filter_cmdline_parser_init(&dummy);
-       f->open = compress_open;
-       f->close = compress_close;
-       f->pre_select = generic_filter_pre_select;
-       f->post_select = compress_post_select;
-       f->parse_config = compress_parse_config;
-       f->free_config = compress_free_config;
-       f->help = (struct ggo_help)DEFINE_GGO_HELP(compress_filter);
+       val = U32_OPTVAL(BLOCKSIZE, lpr);
+       if (val == 0 || val > 31) {
+               PARA_EMERG_LOG("blocksize (%u) out of range\n", val);
+               exit(EXIT_FAILURE);
+       }
+       val = U32_OPTVAL(AGGRESSIVENESS, lpr);
+       if (val > 10) {
+               PARA_EMERG_LOG("aggressiveness (%u) out of range\n", val);
+               exit(EXIT_FAILURE);
+       }
+       val = U32_OPTVAL(INERTIA, lpr);
+       if (val == 0 || val > 14) {
+               PARA_EMERG_LOG("inertia (%u) out of range\n", val);
+               exit(EXIT_FAILURE);
+       }
+       val = U32_OPTVAL(TARGET_LEVEL, lpr);
+       if (val > 32767) {
+               PARA_EMERG_LOG("target-level (%u) out of range\n", val);
+               exit(EXIT_FAILURE);
+       }
+       val = U32_OPTVAL(DAMP, lpr);
+       if (val > 16) {
+               PARA_EMERG_LOG("damp (%u) out of range\n", val);
+               exit(EXIT_FAILURE);
+       }
+       return NULL; /* no need for a config structure */
 }
+
+const struct filter lsg_filter_cmd_com_compress_user_data = {
+       .setup = compress_setup,
+       .open = compress_open,
+       .close = compress_close,
+       .pre_select = generic_filter_pre_select,
+       .post_select = compress_post_select,
+};
index 0b00cc2af9f006d492975a54210ce26f4e5aa56e..f518f89af154933ffaa64401ba1086b7e3f369c9 100644 (file)
@@ -6,7 +6,6 @@ AC_CONFIG_HEADERS([config.h])
 
 AC_CONFIG_FILES([Makefile])
 AC_DEFUN([add_dot_o],[$(for i in $@; do printf "$i.o "; done)])
-AC_DEFUN([add_cmdline],[$(for i in $@; do printf "${i}.cmdline "; done)])
 AC_DEFUN([LIB_ARG_WITH], [
        AC_ARG_WITH($1-headers, [AS_HELP_STRING(--with-$1-headers=dir,
                [look for $1 headers in dir])])
@@ -47,43 +46,22 @@ AC_DEFUN([LIB_SUBST_FLAGS], [
        AC_SUBST($1_cppflags)
        AC_SUBST($1_ldflags)
 ])
+AC_DEFUN([REQUIRE_EXECUTABLE], [
+        AC_PATH_PROG(m4_toupper([$1]), [$1])
+        test -z "$m4_toupper([$1])" && AC_MSG_ERROR(
+               [$1 is required to build this package])
+])
 
 AC_USE_SYSTEM_EXTENSIONS
 AC_C_BIGENDIAN()
-
-AC_PATH_PROG([GENGETOPT], [gengetopt])
-test -z "$GENGETOPT" && AC_MSG_ERROR(
-       [gengetopt is required to build this package])
-
-AC_PATH_PROG([M4], [m4])
-test -z "$M4" && AC_MSG_ERROR(
-       [The m4 macro processor is required to build this package])
-
-AC_PATH_PROG([HELP2MAN], [help2man])
-test -z "$HELP2MAN" && AC_MSG_ERROR(
-       [help2man is required to build this package])
-
-AC_PATH_PROG([INSTALL], [install])
-test -z "$INSTALL" && AC_MSG_ERROR(
-       [The install program is required to build this package])
-
 AC_PROG_CC
 AC_PROG_CPP
 
-executables="recv filter audioc write afh play"
-################################################################## clock_gettime
-clock_gettime_lib=
-AC_CHECK_LIB([c], [clock_gettime], [clock_gettime_lib=c], [
-       AC_CHECK_LIB([rt], [clock_gettime], [clock_gettime_lib=rt], [], [])
-])
-if test -n "$clock_gettime_lib"; then
-       AC_DEFINE(HAVE_CLOCK_GETTIME, 1, [
-               define to 1 if clock_gettime() is supported])
-fi
-if test "$clock_gettime_lib" = "rt"; then
-       AC_SUBST(clock_gettime_ldflags, -lrt)
-fi
+REQUIRE_EXECUTABLE([bison])
+REQUIRE_EXECUTABLE([flex])
+REQUIRE_EXECUTABLE([m4])
 
+executables="recv filter audioc write afh play"
 ########################################################################### osl
 STASH_FLAGS
 LIB_ARG_WITH([osl], [-losl])
@@ -92,6 +70,24 @@ AC_CHECK_HEADER(osl.h, [], [HAVE_OSL=no])
 AC_CHECK_LIB([osl], [osl_open_table], [], [HAVE_OSL=no])
 LIB_SUBST_FLAGS(osl)
 UNSTASH_FLAGS
+######################################################################## lopsub
+HAVE_LOPSUB=yes
+AC_PATH_PROG([LOPSUBGEN], [lopsubgen])
+test -z "$LOPSUBGEN" && HAVE_LOPSUB=no
+STASH_FLAGS
+LIB_ARG_WITH([lopsub], [-llopsub])
+AC_CHECK_HEADER(lopsub.h, [], [HAVE_LOPSUB=no])
+AC_CHECK_LIB([lopsub], [lls_merge], [], [HAVE_LOPSUB=no])
+if test $HAVE_LOPSUB = no; then AC_MSG_ERROR([
+       The lopsub library is required to build this software, but
+       the above checks indicate it is not installed on your system.
+       Run the following command to download a copy.
+               git clone git://git.tuebingen.mpg.de/lopsub.git
+       Install the library, then run this configure script again.
+])
+fi
+LIB_SUBST_FLAGS([lopsub])
+UNSTASH_FLAGS
 ######################################################################## openssl
 STASH_FLAGS
 HAVE_OPENSSL=yes
@@ -100,8 +96,33 @@ AC_CHECK_HEADER(openssl/ssl.h, [], [HAVE_OPENSSL=no])
 AC_CHECK_LIB([crypto], [RAND_bytes], [], [HAVE_OPENSSL=no])
 LIB_SUBST_FLAGS(openssl)
 if test $HAVE_OPENSSL = yes; then
-       AC_CHECK_LIB([crypto], [RSA_set0_key],
-               AC_DEFINE([HAVE_RSA_SET0_KEY], [1], [openssl-1.1]))
+       HAVE_RSA_SET0_KEY=yes
+       AC_CHECK_DECL([RSA_set0_key], [], [], [#include <openssl/rsa.h>])
+       AC_CHECK_LIB([crypto], [RSA_set0_key], [], [])
+       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 &&
+               AC_DEFINE([HAVE_RSA_SET0_KEY], [1], [openssl >= 1.1])
+
+       HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=yes
+       AC_CHECK_DECL([CRYPTO_cleanup_all_ex_data], [],
+               [HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=no],
+               [#include <openssl/rsa.h>])
+       AC_CHECK_LIB([crypto], [CRYPTO_cleanup_all_ex_data], [],
+               [HAVE_CRYPTO_CLEANUP_ALL_EX_DATA=no])
+       test $HAVE_CRYPTO_CLEANUP_ALL_EX_DATA = yes &&
+               AC_DEFINE([HAVE_CRYPTO_CLEANUP_ALL_EX_DATA], [1],
+                       [not available on FreeBSD 12])
+       HAVE_OPENSSL_THREAD_STOP=yes
+       AC_CHECK_DECL([OPENSSL_thread_stop], [],
+               [HAVE_OPENSSL_THREAD_STOP=no],
+               [#include <openssl/crypto.h>])
+       AC_CHECK_LIB([crypto], [OPENSSL_thread_stop], [],
+               [HAVE_OPENSSL_THREAD_STOP=no])
+       test $HAVE_OPENSSL_THREAD_STOP = yes &&
+               AC_DEFINE([HAVE_OPENSSL_THREAD_STOP], [1],
+                       [not available on openssl-1.0])
 fi
 UNSTASH_FLAGS
 ######################################################################### gcrypt
@@ -171,26 +192,23 @@ AC_DEFINE_UNQUOTED(ICONV_CAST, $cast, [cast for second arg to iconv()])
 AC_MSG_RESULT($msg)
 UNSTASH_FLAGS
 ########################################################################### ucred
-AC_MSG_CHECKING(for struct ucred)
-AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+AC_CHECK_TYPE([struct ucred], [
+       AC_DEFINE(HAVE_UCRED, 1, define to 1 you have struct ucred)
+], [], [
        #include <sys/types.h>
        #include <sys/socket.h>
+])
+################################################################### FNM_EXTMATCH
+AC_MSG_CHECKING(for extended wildcard pattern matching)
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+       #include <fnmatch.h>
 ]], [[
-       struct ucred sucred; sucred.pid=0;
-]])],[have_ucred=yes],[have_ucred=no])
-AC_MSG_RESULT($have_ucred)
-if test ${have_ucred} = yes; then
-       AC_DEFINE(HAVE_UCRED, 1, define to 1 you have struct ucred)
+       unsigned n = FNM_EXTMATCH;
+]])], [have_fnm_extmatch=yes], [have_fnm_extmatch=no])
+AC_MSG_RESULT($have_fnm_extmatch)
+if test $have_fnm_extmatch = yes; then
+       AC_DEFINE(HAVE_FNM_EXTMATCH, 1, define to 1 if FNM_EXTMATCH is defined)
 fi
-########################################################################### gengetopt
-echo 'option "z" z "" flag off' | $GENGETOPT --file-name conftest-ggo &&
-AC_CHECK_DECL(
-       [gengetopt_args_info_description],
-       [ggo_descriptions_declared=yes],
-       [ggo_descriptions_declared=no],
-       [#include "conftest-ggo.h"]
-)
-AC_SUBST(ggo_descriptions_declared)
 ########################################################################### curses
 STASH_FLAGS
 LIB_ARG_WITH([curses], [])
@@ -201,36 +219,12 @@ curses_ldflags="$curses_ldflags $LIBS"
 LIB_SUBST_FLAGS(curses)
 UNSTASH_FLAGS
 ########################################################################### ip_mreqn
-AC_MSG_CHECKING(for struct ip_mreqn (UDPv4 multicast))
-AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+AC_CHECK_TYPE([struct ip_mreqn], [
+       AC_DEFINE(HAVE_IP_MREQN, 1, define to 1 if you have struct ip_mreqn)
+], [], [
        #include <netdb.h>
        #include <net/if.h>
-]], [[
-       struct ip_mreqn mn;
-       mn.imr_ifindex = 0;
-]])],[have_ip_mreqn=yes],[have_ip_mreqn=no])
-AC_MSG_RESULT($have_ip_mreqn)
-if test ${have_ip_mreqn} = yes; then
-       AC_DEFINE(HAVE_IP_MREQN, 1, define to 1 you have struct ip_mreqn)
-fi
-########################################################################### osx
-
-AC_MSG_CHECKING(for CoreAudio (MacOs))
-AC_LINK_IFELSE([AC_LANG_PROGRAM([[
-       #include <CoreAudio/CoreAudio.h>
-]], [[
-       AudioDeviceID id;
-]])],[have_core_audio=yes],[have_core_audio=no])
-AC_MSG_RESULT($have_core_audio)
-if test ${have_core_audio} = yes; then
-       f1="-framework CoreAudio"
-       f2="-framework AudioToolbox"
-       f3="-framework AudioUnit"
-       f4="-framework CoreServices"
-       core_audio_ldflags="$f1 $f2 $f3 $f4"
-       AC_SUBST(core_audio_ldflags)
-       AC_DEFINE(HAVE_CORE_AUDIO, 1, define to 1 on Mac Os X)
-fi
+])
 ########################################################################### ogg
 STASH_FLAGS
 LIB_ARG_WITH([ogg], [-logg])
@@ -297,10 +291,12 @@ AC_DEFUN([NEED_FLAC_OBJECTS], [{
 }])
 ########################################################################### faad
 STASH_FLAGS
-LIB_ARG_WITH([faad], [-lfaad])
+LIB_ARG_WITH([faad], [-lfaad -lmp4ff])
 HAVE_FAAD=yes
 AC_CHECK_HEADER(neaacdec.h, [], HAVE_FAAD=no)
+AC_CHECK_HEADER(mp4ff.h, [], HAVE_FAAD=no)
 AC_CHECK_LIB([faad], [NeAACDecOpen], [], HAVE_FAAD=no)
+AC_CHECK_LIB([mp4ff], [mp4ff_meta_get_artist], [], HAVE_FAAD=no)
 LIB_SUBST_FLAGS(faad)
 UNSTASH_FLAGS
 ########################################################################### mad
@@ -377,19 +373,11 @@ AC_CHECK_HEADER(samplerate.h, [], HAVE_SAMPLERATE=no)
 AC_CHECK_LIB([samplerate], [src_process], [], HAVE_SAMPLERATE=no)
 LIB_SUBST_FLAGS(samplerate)
 UNSTASH_FLAGS
-########################################################################## mp4v2
-STASH_FLAGS
-LIB_ARG_WITH([mp4v2], [-lmp4v2])
-HAVE_MP4V2=yes
-AC_CHECK_HEADER([mp4v2/mp4v2.h], [], [HAVE_MP4V2=no])
-AC_CHECK_LIB([mp4v2], [MP4Read], [], [HAVE_MP4V2=no])
-LIB_SUBST_FLAGS(mp4v2)
-UNSTASH_FLAGS
 ######################################################################### server
-if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then
+if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
+               test -n "$FLEX"; then
        build_server="yes"
        executables="$executables server"
-       server_cmdline_objs="server"
        server_errlist_objs="
                server
                afh_common
@@ -414,6 +402,7 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then
                afs
                aft
                mood
+               mp
                score
                attribute
                blob
@@ -428,10 +417,10 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then
                wma_common
                sideband
                version
-               ggo
+               lsu
        "
        if test "$CRYPTOLIB" = openssl; then
-               server_errlist_objs="$server_errlist_objs crypt"
+               server_errlist_objs="$server_errlist_objs openssl"
        else
                server_errlist_objs="$server_errlist_objs gcrypt"
        fi
@@ -440,10 +429,10 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then
        NEED_SPEEX_OBJECTS() && server_errlist_objs="$server_errlist_objs spx_afh spx_common"
        NEED_OPUS_OBJECTS() && server_errlist_objs="$server_errlist_objs opus_afh opus_common"
        NEED_FLAC_OBJECTS && server_errlist_objs="$server_errlist_objs flac_afh"
-       if test $HAVE_FAAD = yes && test $HAVE_MP4V2 = yes; then
-               server_errlist_objs="$server_errlist_objs aac_afh aac_common"
+       if test $HAVE_FAAD = yes; then
+               server_errlist_objs="$server_errlist_objs aac_afh"
        fi
-       server_objs="add_cmdline($server_cmdline_objs) $server_errlist_objs"
+       server_objs="$server_errlist_objs"
        AC_SUBST(server_objs, add_dot_o($server_objs))
 else
        build_server="no"
@@ -452,12 +441,12 @@ fi
 if test -n "$CRYPTOLIB"; then
        build_client="yes"
        executables="$executables client"
-       client_cmdline_objs="client"
        client_errlist_objs="
                client
                net
                string
                fd
+               lsu
                sched
                stdin
                stdout
@@ -468,18 +457,17 @@ if test -n "$CRYPTOLIB"; then
                crypt_common
                base64
                version
-               ggo
        "
        if test "$CRYPTOLIB" = openssl; then
-               client_errlist_objs="$client_errlist_objs crypt"
+               client_errlist_objs="$client_errlist_objs openssl"
        else
                client_errlist_objs="$client_errlist_objs gcrypt"
        fi
        if test $HAVE_READLINE = yes; then
                client_errlist_objs="$client_errlist_objs interactive"
        fi
-       client_objs="add_cmdline($client_cmdline_objs) $client_errlist_objs"
-       AC_SUBST(client_objs, add_dot_o($client_objs))
+       client_objs="$client_errlist_objs"
+       AC_SUBST(client_objs, add_dot_o($client_errlist_objs))
 else
        build_client="no"
 fi
@@ -488,18 +476,6 @@ if test -n "$CRYPTOLIB"; then
        build_audiod="yes"
        executables="$executables audiod"
        audiod_audio_formats="wma"
-       audiod_cmdline_objs="$audiod_cmdline_objs
-               audiod
-               compress_filter
-               http_recv
-               dccp_recv
-               file_write
-               client
-               amp_filter
-               udp_recv
-               prebuffer_filter
-               sync_filter
-       "
        audiod_errlist_objs="$audiod_errlist_objs
                audiod
                signal
@@ -526,7 +502,6 @@ if test -n "$CRYPTOLIB"; then
                audiod_command
                fecdec_filter
                client_common
-               ggo
                udp_recv
                color
                fec
@@ -538,16 +513,13 @@ if test -n "$CRYPTOLIB"; then
                wmadec_filter
                buffer_tree
                sync_filter
+               lsu
        "
        if test "$CRYPTOLIB" = openssl; then
-               audiod_errlist_objs="$audiod_errlist_objs crypt"
+               audiod_errlist_objs="$audiod_errlist_objs openssl"
        else
                audiod_errlist_objs="$audiod_errlist_objs gcrypt"
        fi
-       if test "$have_core_audio" = "yes"; then
-               audiod_errlist_objs="$audiod_errlist_objs osx_write ipc"
-               audiod_cmdline_objs="$audiod_cmdline_objs osx_write"
-       fi
        NEED_VORBIS_OBJECTS && {
                audiod_errlist_objs="$audiod_errlist_objs oggdec_filter"
                audiod_audio_formats="$audiod_audio_formats ogg"
@@ -565,31 +537,26 @@ if test -n "$CRYPTOLIB"; then
                audiod_audio_formats="$audiod_audio_formats flac"
        }
        if test $HAVE_FAAD = yes; then
-               audiod_errlist_objs="$audiod_errlist_objs aacdec_filter aac_common"
+               audiod_errlist_objs="$audiod_errlist_objs aacdec_filter"
                audiod_audio_formats="$audiod_audio_formats aac"
        fi
        if test $HAVE_MAD = yes; then
                audiod_audio_formats="$audiod_audio_formats mp3"
-               audiod_cmdline_objs="$audiod_cmdline_objs mp3dec_filter"
                audiod_errlist_objs="$audiod_errlist_objs mp3dec_filter"
        fi
        if test $HAVE_OSS = yes; then
                audiod_errlist_objs="$audiod_errlist_objs oss_write"
-               audiod_cmdline_objs="$audiod_cmdline_objs oss_write"
        fi
        if test $HAVE_ALSA = yes; then
                audiod_errlist_objs="$audiod_errlist_objs alsa_write"
-               audiod_cmdline_objs="$audiod_cmdline_objs alsa_write"
        fi
        NEED_AO_OBJECTS && {
                audiod_errlist_objs="$audiod_errlist_objs ao_write"
-               audiod_cmdline_objs="$audiod_cmdline_objs ao_write"
        }
        if test $HAVE_SAMPLERATE = yes; then
                audiod_errlist_objs="$audiod_errlist_objs resample_filter check_wav"
-               audiod_cmdline_objs="$audiod_cmdline_objs resample_filter"
        fi
-       audiod_objs="add_cmdline($audiod_cmdline_objs) $audiod_errlist_objs"
+       audiod_objs="$audiod_errlist_objs"
        AC_SUBST(audiod_objs, add_dot_o($audiod_objs))
 
        enum="$(for i in $audiod_audio_formats; do printf "AUDIO_FORMAT_${i}, " | tr '[a-z]' '[A-Z]'; done)"
@@ -600,53 +567,27 @@ if test -n "$CRYPTOLIB"; then
 else
        build_audiod="no"
 fi
-########################################################################### fade
+########################################################################### mixer
 if test $HAVE_OSS = yes -o $HAVE_ALSA = yes; then
-       build_fade="yes"
-       executables="$executables fade"
-       fade_cmdline_objs="fade"
-       fade_errlist_objs="fade exec string fd version ggo"
+       build_mixer="yes"
+       executables="$executables mixer"
+       mixer_errlist_objs="mixer exec string fd lsu version"
        if test $HAVE_OSS = yes; then
-               fade_errlist_objs="$fade_errlist_objs oss_mix"
-               mixers="${mixers}oss "
-               default_mixer="OSS_MIX"
+               mixer_errlist_objs="$mixer_errlist_objs oss_mix"
        fi
        if test $HAVE_ALSA = yes; then
-               fade_errlist_objs="$fade_errlist_objs alsa_mix"
-               mixers="${mixers}alsa "
-               default_mixer="ALSA_MIX"
+               mixer_errlist_objs="$mixer_errlist_objs alsa_mix"
        fi
-       fade_objs="add_cmdline($fade_cmdline_objs) $fade_errlist_objs"
-       AC_SUBST(fade_objs, add_dot_o($fade_objs))
-       enum="$(
-               for i in $mixers; do
-                       printf "${i}_MIX, " | tr '[a-z]' '[A-Z]'
-               done
-       )"
-       AC_DEFINE_UNQUOTED(MIXER_ENUM, $enum NUM_SUPPORTED_MIXERS,
-               enum of supported mixers)
-       AC_DEFINE_UNQUOTED(DEFAULT_MIXER, $default_mixer,
-               use this mixer if none was specified)
-       names="$(for i in $mixers; do printf \"$i\",' ' ; done)"
-       AC_DEFINE_UNQUOTED(MIXER_NAMES, $names, supported mixer names)
-       inits="$(
-               for i in $mixers; do
-                       printf 'extern void '$i'_mix_init(struct mixer *); '
-               done
-       )"
-       AC_DEFINE_UNQUOTED(DECLARE_MIXER_INITS, $inits,
-               init functions of the supported mixers)
-       array="$(for i in $mixers; do printf '{.init = '$i'_mix_init},'; done)"
-       AC_DEFINE_UNQUOTED(MIXER_ARRAY, $array, array of supported mixers)
+       mixer_objs="$mixer_errlist_objs"
+       AC_SUBST(mixer_objs, add_dot_o($mixer_objs))
 else
-       build_fade="no"
+       build_mixer="no"
        AC_MSG_WARN([no mixer support])
 fi
 ########################################################################### gui
 if test $HAVE_CURSES = yes; then
        build_gui="yes"
        executables="$executables gui"
-       gui_cmdline_objs="gui"
        gui_errlist_objs="
                exec
                signal
@@ -656,27 +597,18 @@ if test $HAVE_CURSES = yes; then
                fd
                gui
                gui_theme
+               lsu
                time
                sched
                version
-               ggo
        "
-       gui_objs="add_cmdline($gui_cmdline_objs) $gui_errlist_objs"
+       gui_objs="$gui_errlist_objs"
        AC_SUBST(gui_objs, add_dot_o($gui_objs))
 else
        build_gui="no"
        AC_MSG_WARN([no curses lib, cannot build para_gui])
 fi
 ######################################################################## filter
-filters="
-       compress
-       wav
-       amp
-       fecdec
-       wmadec
-       prebuffer
-       sync
-"
 filter_errlist_objs="
        filter_common
        wav_filter
@@ -688,9 +620,9 @@ filter_errlist_objs="
        sched
        fd
        amp_filter
-       ggo
        fecdec_filter
        fec
+       lsu
        version
        prebuffer_filter
        time
@@ -702,65 +634,23 @@ filter_errlist_objs="
        net
        sync_filter
 "
-filter_cmdline_objs="
-       filter
-       compress_filter
-       amp_filter
-       prebuffer_filter
-       sync_filter
-"
-NEED_VORBIS_OBJECTS && {
-       filters="$filters oggdec"
-       filter_errlist_objs="$filter_errlist_objs oggdec_filter"
-}
-NEED_SPEEX_OBJECTS && {
-       filters="$filters spxdec"
-       filter_errlist_objs="$filter_errlist_objs spxdec_filter spx_common"
-}
-NEED_OPUS_OBJECTS && {
-       filters="$filters opusdec"
-       filter_errlist_objs="$filter_errlist_objs opusdec_filter opus_common"
-}
-NEED_FLAC_OBJECTS && {
-       filter_errlist_objs="$filter_errlist_objs flacdec_filter"
-       filters="$filters flacdec"
-}
+NEED_VORBIS_OBJECTS && filter_errlist_objs="$filter_errlist_objs oggdec_filter"
+NEED_SPEEX_OBJECTS && filter_errlist_objs="$filter_errlist_objs spxdec_filter spx_common"
+NEED_OPUS_OBJECTS && filter_errlist_objs="$filter_errlist_objs opusdec_filter opus_common"
+NEED_FLAC_OBJECTS && filter_errlist_objs="$filter_errlist_objs flacdec_filter"
 if test $HAVE_FAAD = yes; then
-       filter_errlist_objs="$filter_errlist_objs aacdec_filter aac_common"
-       filters="$filters aacdec"
+       filter_errlist_objs="$filter_errlist_objs aacdec_filter"
 fi
 if test $HAVE_MAD = yes; then
-       filter_cmdline_objs="$filter_cmdline_objs mp3dec_filter"
        filter_errlist_objs="$filter_errlist_objs mp3dec_filter"
-       filters="$filters mp3dec"
 fi
 if test $HAVE_SAMPLERATE = yes; then
        filter_errlist_objs="$filter_errlist_objs resample_filter check_wav"
-       filter_cmdline_objs="$filter_cmdline_objs resample_filter"
-       filters="$filters resample"
 fi
-filters="$(echo $filters)"
-AC_SUBST(filters)
-filter_objs="add_cmdline($filter_cmdline_objs) $filter_errlist_objs"
+filter_objs="$filter_errlist_objs"
 
 AC_SUBST(filter_objs, add_dot_o($filter_objs))
-
-enum="$(for i in $filters; do printf "${i}_FILTER, " | tr '[a-z]' '[A-Z]'; done)"
-AC_DEFINE_UNQUOTED(FILTER_ENUM, $enum NUM_SUPPORTED_FILTERS,
-       enum of supported filters)
-inits="$(for i in $filters; do printf 'extern void '$i'_filter_init(struct filter *f); '; done)"
-AC_DEFINE_UNQUOTED(DECLARE_FILTER_INITS, $inits, init functions of the supported filters)
-array="$(for i in $filters; do printf '{.name = "'$i'", .init = '$i'_filter_init},'; done)"
-AC_DEFINE_UNQUOTED(FILTER_ARRAY, $array, array of supported filters)
 ########################################################################## recv
-recv_cmdline_objs="
-       recv
-       http_recv
-       dccp_recv
-       udp_recv
-       afh_recv
-"
-
 recv_errlist_objs="
        http_recv
        recv_common
@@ -772,7 +662,6 @@ recv_errlist_objs="
        fd
        sched
        stdout
-       ggo
        udp_recv
        buffer_tree
        afh_recv
@@ -788,15 +677,13 @@ NEED_SPEEX_OBJECTS && recv_errlist_objs="$recv_errlist_objs spx_afh spx_common"
 NEED_OPUS_OBJECTS && recv_errlist_objs="$recv_errlist_objs opus_afh opus_common"
 NEED_FLAC_OBJECTS && recv_errlist_objs="$recv_errlist_objs flac_afh"
 
-if test $HAVE_FAAD = yes -a $HAVE_MP4V2 = yes; then
-       recv_errlist_objs="$recv_errlist_objs aac_afh aac_common"
+if test $HAVE_FAAD = yes; then
+       recv_errlist_objs="$recv_errlist_objs aac_afh"
 fi
-recv_objs="add_cmdline($recv_cmdline_objs) $recv_errlist_objs"
-AC_SUBST(receivers, "http dccp udp afh")
+recv_objs="$recv_errlist_objs"
 AC_SUBST(recv_objs, add_dot_o($recv_objs))
 ########################################################################### afh
 audio_format_handlers="mp3 wma"
-afh_cmdline_objs="afh"
 afh_errlist_objs="
        afh
        string
@@ -807,7 +694,6 @@ afh_errlist_objs="
        wma_afh
        wma_common
        version
-       ggo
 "
 NEED_OGG_OBJECTS && afh_errlist_objs="$afh_errlist_objs ogg_afh_common"
 NEED_VORBIS_OBJECTS && {
@@ -826,12 +712,12 @@ NEED_FLAC_OBJECTS && {
        afh_errlist_objs="$afh_errlist_objs flac_afh"
        audio_format_handlers="$audio_format_handlers flac"
 }
-if test $HAVE_FAAD = yes -a $HAVE_MP4V2 = yes; then
-       afh_errlist_objs="$afh_errlist_objs aac_afh aac_common"
+if test $HAVE_FAAD = yes; then
+       afh_errlist_objs="$afh_errlist_objs aac_afh"
        audio_format_handlers="$audio_format_handlers aac"
 fi
 
-afh_objs="add_cmdline($afh_cmdline_objs) $afh_errlist_objs"
+afh_objs="$afh_errlist_objs"
 
 AC_SUBST(afh_objs, add_dot_o($afh_objs))
 ########################################################################## play
@@ -839,7 +725,6 @@ play_errlist_objs="
        play
        fd
        sched
-       ggo
        buffer_tree
        time
        string
@@ -867,23 +752,8 @@ play_errlist_objs="
        file_write
        version
        sync_filter
+       lsu
 "
-play_cmdline_objs="
-       http_recv
-       dccp_recv
-       udp_recv
-       afh_recv
-       compress_filter
-       amp_filter
-       prebuffer_filter
-       file_write
-       play
-       sync_filter
-"
-if test "$have_core_audio" = "yes"; then
-       play_errlist_objs="$play_errlist_objs osx_write ipc"
-       play_cmdline_objs="$play_cmdline_objs osx_write"
-fi
 NEED_OGG_OBJECTS && play_errlist_objs="$play_errlist_objs ogg_afh_common"
 NEED_VORBIS_OBJECTS && {
        play_errlist_objs="$play_errlist_objs oggdec_filter ogg_afh"
@@ -901,45 +771,30 @@ NEED_FLAC_OBJECTS && {
        play_errlist_objs="$play_errlist_objs flacdec_filter flac_afh"
 }
 if test $HAVE_FAAD = yes; then
-       play_errlist_objs="$play_errlist_objs aacdec_filter"
-fi
-if test $HAVE_MP4V2 = yes; then
-       play_errlist_objs="$play_errlist_objs aac_afh"
-fi
-if test $HAVE_MP4V2 = yes || test $HAVE_FAAD = yes; then
-       play_errlist_objs="$play_errlist_objs aac_common"
+       play_errlist_objs="$play_errlist_objs aac_afh aacdec_filter"
 fi
 if test $HAVE_MAD = yes; then
-       play_cmdline_objs="$play_cmdline_objs mp3dec_filter"
        play_errlist_objs="$play_errlist_objs mp3dec_filter"
 fi
 if test $HAVE_OSS = yes; then
        play_errlist_objs="$play_errlist_objs oss_write"
-       play_cmdline_objs="$play_cmdline_objs oss_write"
 fi
 if test $HAVE_ALSA = yes; then
        play_errlist_objs="$play_errlist_objs alsa_write"
-       play_cmdline_objs="$play_cmdline_objs alsa_write"
 fi
 NEED_AO_OBJECTS && {
        play_errlist_objs="$play_errlist_objs ao_write"
-       play_cmdline_objs="$play_cmdline_objs ao_write"
 }
 if test $HAVE_READLINE = yes; then
        play_errlist_objs="$play_errlist_objs interactive"
 fi
 if test $HAVE_SAMPLERATE = yes; then
        play_errlist_objs="$play_errlist_objs resample_filter check_wav"
-       play_cmdline_objs="$play_cmdline_objs resample_filter"
 fi
 
-play_objs="add_cmdline($play_cmdline_objs) $play_errlist_objs"
+play_objs="$play_errlist_objs"
 AC_SUBST(play_objs, add_dot_o($play_objs))
 ######################################################################### write
-write_cmdline_objs="
-       write
-       file_write
-"
 write_errlist_objs="
        write
        write_common
@@ -950,59 +805,29 @@ write_errlist_objs="
        sched
        stdin
        buffer_tree
-       ggo
        check_wav
        version
 "
-writers="file"
-default_writer="FILE_WRITE"
 
-if test "$have_core_audio" = "yes"; then
-       write_errlist_objs="$write_errlist_objs osx_write ipc"
-       write_cmdline_objs="$write_cmdline_objs osx_write"
-       writers="$writers osx"
-       default_writer="OSX_WRITE"
-fi
 NEED_AO_OBJECTS && {
        write_errlist_objs="$write_errlist_objs ao_write"
-       write_cmdline_objs="$write_cmdline_objs ao_write"
-       writers="$writers ao"
-       default_writer="AO_WRITE"
 }
 if test $HAVE_OSS = yes; then
        write_errlist_objs="$write_errlist_objs oss_write"
-       write_cmdline_objs="$write_cmdline_objs oss_write"
-       writers="$writers oss"
-       default_writer="OSS_WRITE"
 fi
 if test $HAVE_ALSA = yes; then
        write_errlist_objs="$write_errlist_objs alsa_write"
-       write_cmdline_objs="$write_cmdline_objs alsa_write"
-       writers="$writers alsa"
-       default_writer="ALSA_WRITE"
 fi
-AC_SUBST(writers)
-write_objs="add_cmdline($write_cmdline_objs) $write_errlist_objs"
+write_objs="$write_errlist_objs"
 AC_SUBST(write_objs, add_dot_o($write_objs))
-enum="$(for i in $writers; do printf "${i}_WRITE, " | tr '[a-z]' '[A-Z]'; done)"
-AC_DEFINE_UNQUOTED(WRITER_ENUM, $enum NUM_SUPPORTED_WRITERS,
-       enum of supported writers)
-AC_DEFINE_UNQUOTED(DEFAULT_WRITER, $default_writer, use this writer if none was specified)
-names="$(for i in $writers; do printf \"$i\",' ' ; done)"
-AC_DEFINE_UNQUOTED(WRITER_NAMES, $names, supported writer names)
-inits="$(for i in $writers; do printf 'extern void '$i'_write_init(struct writer *); '; done)"
-AC_DEFINE_UNQUOTED(DECLARE_WRITER_INITS, $inits, init functions of the supported writers)
-array="$(for i in $writers; do printf '{.init = '$i'_write_init},'; done)"
-AC_DEFINE_UNQUOTED(WRITER_ARRAY, $array, array of supported writers)
 ######################################################################## audioc
-audioc_cmdline_objs="audioc"
 audioc_errlist_objs="
        audioc
        string
+       lsu
        net
        fd
        version
-       ggo
 "
 if test $HAVE_READLINE = yes; then
        audioc_errlist_objs="$audioc_errlist_objs
@@ -1012,35 +837,11 @@ if test $HAVE_READLINE = yes; then
                time
        "
 fi
-audioc_objs="add_cmdline($audioc_cmdline_objs) $audioc_errlist_objs"
+audioc_objs="$audioc_errlist_objs"
 AC_SUBST(audioc_objs, add_dot_o($audioc_objs))
-################################################################## status items
-
-status_items="basename status num_played mtime bitrate frequency file_size
-status_flags format score techinfo afs_mode
-attributes_txt decoder_flags audiod_status play_time attributes_bitmap
-offset seconds_total stream_start current_time audiod_uptime image_id
-lyrics_id duration directory lyrics_name image_name path hash channels
-last_played num_chunks chunk_time amplification artist title year album
-comment"
-
-result=
-for i in $status_items; do
-       result="$result SI_$(echo $i | tr 'a-z' 'A-Z'), "
-done
-AC_DEFINE_UNQUOTED(STATUS_ITEM_ENUM, [$result],
-       [enum of all status items])
-
-result=
-for i in $status_items; do
-       result="$result \"$i\", "
-done
-AC_DEFINE_UNQUOTED(STATUS_ITEM_ARRAY, [$result],
-       [char * array of all status items])
 
 AC_DEFINE_UNQUOTED(AUDIO_FORMAT_HANDLERS, "$audio_format_handlers",
        [formats supported by para_server and para_afh])
-
 AC_SUBST(executables)
 
 AC_OUTPUT
@@ -1052,15 +853,11 @@ unix socket credentials: $have_ucred
 readline (interactive CLIs): $HAVE_READLINE
 id3 version 2 support: $HAVE_ID3TAG
 faad: $HAVE_FAAD
-mp4v2: $HAVE_MP4V2
-
 audio format handlers: $audio_format_handlers
-filters: $(echo $filters)
-writers: $writers
 
 para_server: $build_server
 para_gui: $build_gui
-para_fade: $build_fade
+para_mixer: $build_mixer
 para_client: $build_client
 para_audiod: $build_audiod
 ])
diff --git a/crypt.c b/crypt.c
deleted file mode 100644 (file)
index 8116fb6..0000000
--- a/crypt.c
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file crypt.c Openssl-based encryption/decryption routines. */
-
-#include <regex.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <openssl/rand.h>
-#include <openssl/err.h>
-#include <openssl/rc4.h>
-#include <openssl/pem.h>
-#include <openssl/sha.h>
-#include <openssl/bn.h>
-#include <openssl/aes.h>
-
-#include "para.h"
-#include "error.h"
-#include "string.h"
-#include "crypt.h"
-#include "fd.h"
-#include "crypt_backend.h"
-#include "base64.h"
-
-struct asymmetric_key {
-       RSA *rsa;
-};
-
-void get_random_bytes_or_die(unsigned char *buf, int num)
-{
-       unsigned long err;
-
-       /* RAND_bytes() returns 1 on success, 0 otherwise. */
-       if (RAND_bytes(buf, num) == 1)
-               return;
-       err = ERR_get_error();
-       PARA_EMERG_LOG("%s\n", ERR_reason_error_string(err));
-       exit(EXIT_FAILURE);
-}
-
-/*
- * Read 64 bytes from /dev/urandom and adds them to the SSL PRNG. Seed the PRNG
- * used by random() with a random seed obtained from SSL. If /dev/random 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().
- */
-void init_random_seed_or_die(void)
-{
-       int seed, ret = RAND_load_file("/dev/urandom", 64);
-
-       if (ret != 64) {
-               PARA_EMERG_LOG("could not seed PRNG (ret = %d)\n", ret);
-               exit(EXIT_FAILURE);
-       }
-       get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed));
-       srandom(seed);
-}
-
-static EVP_PKEY *load_key(const char *file, int private)
-{
-       BIO *key;
-       EVP_PKEY *pkey = NULL;
-       int ret = check_key_file(file, private);
-
-       if (ret < 0) {
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-               return NULL;
-       }
-       key = BIO_new(BIO_s_file());
-       if (!key)
-               return NULL;
-       if (BIO_read_filename(key, file) > 0) {
-               if (private == LOAD_PRIVATE_KEY)
-                       pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
-               else
-                       pkey = PEM_read_bio_PUBKEY(key, NULL, NULL, NULL);
-       }
-       BIO_free(key);
-       return pkey;
-}
-
-static int get_openssl_key(const char *key_file, RSA **rsa, int private)
-{
-       EVP_PKEY *key = load_key(key_file, private);
-
-       if (!key)
-               return (private == LOAD_PRIVATE_KEY)? -E_PRIVATE_KEY
-                       : -E_PUBLIC_KEY;
-       *rsa = EVP_PKEY_get1_RSA(key);
-       EVP_PKEY_free(key);
-       if (!*rsa)
-               return -E_RSA;
-       return RSA_size(*rsa);
-}
-
-/*
- * The public key loading functions below were inspired by corresponding code
- * of openssh-5.2p1, Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo,
- * Finland. However, not much of the original code remains.
- */
-
-static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result)
-{
-       const unsigned char *p = buf, *end = buf + len;
-       uint32_t bnsize;
-       BIGNUM *bn;
-
-       if (p + 4 < p)
-               return -E_BIGNUM;
-       if (p + 4 > end)
-               return -E_BIGNUM;
-       bnsize = read_ssh_u32(p);
-       PARA_DEBUG_LOG("bnsize: %u\n", bnsize);
-       p += 4;
-       if (p + bnsize < p)
-               return -E_BIGNUM;
-       if (p + bnsize > end)
-               return -E_BIGNUM;
-       if (bnsize > 8192)
-               return -E_BIGNUM;
-       bn = BN_bin2bn(p, bnsize, NULL);
-       if (!bn)
-               return -E_BIGNUM;
-       *result = bn;
-       return bnsize + 4;
-}
-
-static int read_rsa_bignums(const unsigned char *blob, int blen, RSA **result)
-{
-       int ret;
-       RSA *rsa;
-       BIGNUM *n, *e;
-       const unsigned char *p = blob, *end = blob + blen;
-
-       rsa = RSA_new();
-       if (!rsa)
-               return -E_BIGNUM;
-       ret = read_bignum(p, end - p, &e);
-       if (ret < 0)
-               goto fail;
-       p += ret;
-       ret = read_bignum(p, end - p, &n);
-       if (ret < 0)
-               goto fail;
-#ifdef HAVE_RSA_SET0_KEY
-       RSA_set0_key(rsa, n, e, NULL);
-#else
-       rsa->n = n;
-       rsa->e = e;
-#endif
-       *result = rsa;
-       return 1;
-fail:
-       RSA_free(rsa);
-       return ret;
-}
-
-int get_asymmetric_key(const char *key_file, int private,
-               struct asymmetric_key **result)
-{
-       struct asymmetric_key *key = NULL;
-       void *map = NULL;
-       unsigned char *blob = NULL;
-       size_t map_size, encoded_size, decoded_size;
-       int ret, ret2;
-       char *cp;
-
-       key = para_malloc(sizeof(*key));
-       if (private) {
-               ret = get_openssl_key(key_file, &key->rsa, LOAD_PRIVATE_KEY);
-               goto out;
-       }
-       ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
-       if (ret < 0)
-               goto out;
-       ret = is_ssh_rsa_key(map, map_size);
-       if (!ret) {
-               ret = para_munmap(map, map_size);
-               map = NULL;
-               if (ret < 0)
-                       goto out;
-               ret = get_openssl_key(key_file, &key->rsa, LOAD_PUBLIC_KEY);
-               goto out;
-       }
-       cp = map + ret;
-       encoded_size = map_size - ret;
-       PARA_INFO_LOG("decoding public rsa-ssh key %s\n", key_file);
-       ret = uudecode(cp, encoded_size, (char **)&blob, &decoded_size);
-       if (ret < 0)
-               goto out_unmap;
-       ret = check_ssh_key_header(blob, decoded_size);
-       if (ret < 0)
-               goto out_unmap;
-       ret = read_rsa_bignums(blob + ret, decoded_size - ret, &key->rsa);
-       if (ret < 0)
-               goto out_unmap;
-       ret = RSA_size(key->rsa);
-out_unmap:
-       ret2 = para_munmap(map, map_size);
-       if (ret >= 0 && ret2 < 0)
-               ret = ret2;
-out:
-       if (ret < 0) {
-               free(key);
-               *result = NULL;
-               PARA_ERROR_LOG("key %s: %s\n", key_file, para_strerror(-ret));
-       } else
-               *result = key;
-       free(blob);
-       return ret;
-}
-
-void free_asymmetric_key(struct asymmetric_key *key)
-{
-       if (!key)
-               return;
-       RSA_free(key->rsa);
-       free(key);
-}
-
-int priv_decrypt(const char *key_file, unsigned char *outbuf,
-               unsigned char *inbuf, int inlen)
-{
-       struct asymmetric_key *priv;
-       int ret;
-
-       if (inlen < 0)
-               return -E_RSA;
-       ret = get_asymmetric_key(key_file, LOAD_PRIVATE_KEY, &priv);
-       if (ret < 0)
-               return ret;
-       /*
-        * RSA is vulnerable to timing attacks. Generate a random blinding
-        * factor to protect against this kind of attack.
-        */
-       ret = -E_BLINDING;
-       if (RSA_blinding_on(priv->rsa, NULL) == 0)
-               goto out;
-       ret = RSA_private_decrypt(inlen, inbuf, outbuf, priv->rsa,
-               RSA_PKCS1_OAEP_PADDING);
-       RSA_blinding_off(priv->rsa);
-       if (ret <= 0)
-               ret = -E_DECRYPT;
-out:
-       free_asymmetric_key(priv);
-       return ret;
-}
-
-int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
-               unsigned len, unsigned char *outbuf)
-{
-       int ret, flen = len; /* RSA_public_encrypt expects a signed int */
-
-       if (flen < 0)
-               return -E_ENCRYPT;
-       ret = RSA_public_encrypt(flen, inbuf, outbuf, pub->rsa,
-               RSA_PKCS1_OAEP_PADDING);
-       return ret < 0? -E_ENCRYPT : ret;
-}
-
-struct stream_cipher {
-       bool use_aes;
-       union {
-               RC4_KEY rc4_key;
-               EVP_CIPHER_CTX *aes;
-       } context;
-};
-
-struct stream_cipher *sc_new(const unsigned char *data, int len,
-               bool use_aes)
-{
-       struct stream_cipher *sc = para_malloc(sizeof(*sc));
-
-       sc->use_aes = use_aes;
-       if (!use_aes) {
-               RC4_set_key(&sc->context.rc4_key, len, data);
-               return sc;
-       }
-       assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
-       sc->context.aes = EVP_CIPHER_CTX_new();
-       EVP_EncryptInit_ex(sc->context.aes, EVP_aes_128_ctr(), NULL, data,
-               data + AES_CRT128_BLOCK_SIZE);
-       return sc;
-}
-
-void sc_free(struct stream_cipher *sc)
-{
-       if (!sc)
-               return;
-       EVP_CIPHER_CTX_free(sc->context.aes);
-       free(sc);
-}
-
-/**
- * The RC4() implementation of openssl apparently reads and writes data in
- * blocks of 8 bytes. So we have to make sure our buffer sizes are a multiple
- * of this.
- */
-#define RC4_ALIGN 8
-
-static void rc4_crypt(RC4_KEY *key, struct iovec *src, struct iovec *dst)
-{
-       size_t len = src->iov_len, l1, l2;
-
-       assert(len > 0);
-       assert(len < ((typeof(src->iov_len))-1) / 2);
-       l1 = ROUND_DOWN(len, RC4_ALIGN);
-       l2 = ROUND_UP(len, RC4_ALIGN);
-
-       *dst = (typeof(*dst)) {
-               /* Add one for the terminating zero byte. */
-               .iov_base = para_malloc(l2 + 1),
-               .iov_len = len
-       };
-       RC4(key, l1, src->iov_base, dst->iov_base);
-       if (len > l1) {
-               unsigned char remainder[RC4_ALIGN] = "";
-               memcpy(remainder, src->iov_base + l1, len - l1);
-               RC4(key, len - l1, remainder, dst->iov_base + l1);
-       }
-       ((char *)dst->iov_base)[len] = '\0';
-}
-
-static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src,
-               struct iovec *dst)
-{
-       int ret, inlen = src->iov_len, outlen, tmplen;
-
-       *dst = (typeof(*dst)) {
-               /* Add one for the terminating zero byte. */
-               .iov_base = para_malloc(inlen + 1),
-               .iov_len = inlen
-       };
-       ret = EVP_EncryptUpdate(ctx, dst->iov_base, &outlen, src->iov_base, inlen);
-       assert(ret != 0);
-       ret = EVP_EncryptFinal_ex(ctx, dst->iov_base + outlen, &tmplen);
-       assert(ret != 0);
-       outlen += tmplen;
-       ((char *)dst->iov_base)[outlen] = '\0';
-       dst->iov_len = outlen;
-}
-
-void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
-{
-       if (sc->use_aes)
-               return aes_ctr128_crypt(sc->context.aes, src, dst);
-       return rc4_crypt(&sc->context.rc4_key, src, dst);
-}
-
-void hash_function(const char *data, unsigned long len, unsigned char *hash)
-{
-       SHA_CTX c;
-       SHA1_Init(&c);
-       SHA1_Update(&c, data, len);
-       SHA1_Final(hash, &c);
-}
diff --git a/crypt.h b/crypt.h
index 9be7a23e6da4d6d1796d4d98da7d8280152c6c2a..9623a0035a3b1a89818bd99c3c09c1dac041519b 100644 (file)
--- a/crypt.h
+++ b/crypt.h
@@ -1,20 +1,15 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file crypt.h Public crypto interface. */
 
+/*
+ * Asymmetric pubkey cryptosystem (apc).
+ *
+ * This is just RSA, but this fact is a hidden implementation detail.
+ */
 
-/* These are used to distinguish between loading of private/public key. */
-
-/** The key to load is a public key. */
-#define LOAD_PUBLIC_KEY 0
-/** The key to load is a private key. */
-#define LOAD_PRIVATE_KEY 1
 /** The size of the challenge sent to the client. */
-#define CHALLENGE_SIZE 64
+#define APC_CHALLENGE_SIZE 64
 
 /** Opaque structure for public and private keys. */
 struct asymmetric_key;
@@ -29,7 +24,7 @@ struct asymmetric_key;
  *
  * \return The size of the encrypted data on success, negative on errors.
  */
-int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
+int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
                unsigned len, unsigned char *outbuf);
 
 /**
@@ -44,29 +39,28 @@ int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
  *
  * \return The size of the recovered plaintext on success, negative on errors.
  */
-int priv_decrypt(const char *key_file, unsigned char *outbuf,
+int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
                unsigned char *inbuf, int inlen);
 
 /**
  * Read an asymmetric key from a file.
  *
  * \param key_file The file containing the key.
- * \param private if non-zero, read the private key, otherwise the public key.
  * \param result The key structure is returned here.
  *
  * \return The size of the key on success, negative on errors.
  */
-int get_asymmetric_key(const char *key_file, int private,
-               struct asymmetric_key **result);
+int apc_get_pubkey(const char *key_file, struct asymmetric_key **result);
 
 /**
- * Deallocate an asymmetric key structure.
+ * Deallocate a public key.
  *
  * \param key Pointer to the key structure to free.
  *
- * This must be called for any key obtained by get_asymmetric_key().
+ * This should be called for keys obtained by \ref apc_get_pubkey() if the key is no
+ * longer needed.
  */
-void free_asymmetric_key(struct asymmetric_key *key);
+void apc_free_pubkey(struct asymmetric_key *key);
 
 
 /**
@@ -83,17 +77,20 @@ void free_asymmetric_key(struct asymmetric_key *key);
 void get_random_bytes_or_die(unsigned char *buf, int num);
 
 /**
- * Seed pseudo random number generators.
+ * Initialize the crypto backend.
  *
- * This function seeds the PRNG used by random() with a random seed obtained
- * from the crypto implementation. On errors, an error message is logged and
- * the function calls exit().
+ * This function initializes the crypto library and seeds the pseudo random
+ * number generator used by random() with a random seed obtained from the
+ * crypto implementation. On errors, an error message is logged and the
+ * function calls exit().
  *
  * \sa \ref get_random_bytes_or_die(), srandom(3), random(3), \ref
  * para_random().
  */
-void init_random_seed_or_die(void);
+void crypt_init(void);
 
+/** Allocate all resources of the crypto backend. */
+void crypt_shutdown(void);
 
 /** Opaque structure for stream ciphers. */
 struct stream_cipher;
@@ -119,16 +116,14 @@ struct stream_cipher_context {
 };
 
 /**
- * Allocate and initialize a stream cipher structure.
+ * Allocate and initialize an aes_ctr128 stream cipher structure.
  *
  * \param data The key.
  * \param len The size of the key.
- * \param use_aes True: Use the aes_ctr128 stream cipher, false: Use RC4.
  *
  * \return A new stream cipher structure.
  */
-struct stream_cipher *sc_new(const unsigned char *data, int len,
-               bool use_aes);
+struct stream_cipher *sc_new(const unsigned char *data, int len);
 
 /**
  * Encrypt or decrypt a buffer using a stream cipher.
@@ -163,7 +158,7 @@ _static_inline_ void sc_trafo(struct iovec *src, struct iovec *dst,
 /**
  * Deallocate a stream cipher structure.
  *
- * \param sc A stream cipher previously obtained by sc_new().
+ * \param sc A stream cipher previously obtained by \ref sc_new().
  */
 void sc_free(struct stream_cipher *sc);
 
@@ -193,7 +188,7 @@ void hash_function(const char *data, unsigned long len, unsigned char *hash);
  * will be filled by the function with the ascii representation of the hash
  * value given by \a hash, and a terminating \p NULL byte.
  */
-void hash_to_asc(unsigned char *hash, char *asc);
+void hash_to_asc(const unsigned char *hash, char *asc);
 
 /**
  * Compare two hashes.
@@ -204,4 +199,4 @@ void hash_to_asc(unsigned char *hash, char *asc);
  * \return 1, -1, or zero, depending on whether \a h1 is greater than,
  * less than or equal to h2, respectively.
  */
-int hash_compare(unsigned char *h1, unsigned char *h2);
+int hash_compare(const unsigned char *h1, const unsigned char *h2);
index f9a69d9490057382126e4efe04a15bfda9025419..b0998d8513f35303243ccd632abc3c0aa46f6377 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file crypt_backend.h Non-public crypto interface. */
 
@@ -11,7 +7,13 @@
 /** AES block size in bytes. */
 #define AES_CRT128_BLOCK_SIZE 16
 
-size_t is_ssh_rsa_key(char *data, size_t size);
-uint32_t read_ssh_u32(const void *vp);
-int check_ssh_key_header(const unsigned char *blob, int blen);
-int check_key_file(const char *file, bool private_key);
+int decode_public_key(const char *filename, unsigned char **blob,
+               size_t *decoded_size);
+int decode_private_key(const char *key_file, unsigned char **result,
+               size_t *blob_size);
+/** Legacy PEM keys (openssh-7.7 and earlier, paraslash.0.6.2 and earlier) */
+#define PKT_PEM (0)
+/** OPENSSH keys (since openssh-7.8, paraslash.0.6.3) */
+#define PKT_OPENSSH (1)
+int check_private_key_file(const char *file);
+int find_openssh_bignum_offset(const unsigned char *data, int len);
index a05572df85b1de5fb5e1043abfc34c66e26c65e7..ff24e356ab8d837f8d59d67a3c983d5264376db1 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file crypt_common.c Crypto functions independent of openssl/libgcrypt. */
 
 #include "string.h"
 #include "crypt.h"
 #include "crypt_backend.h"
+#include "portable_io.h"
+#include "fd.h"
+#include "base64.h"
 
 /** If the key begins with this text, we treat it as an ssh key. */
 #define KEY_TYPE_TXT "ssh-rsa"
 
-/**
- * Check if given buffer starts with a ssh rsa key signature.
- *
- * \param data The buffer.
- * \param size Number of data bytes.
+/*
+ * Check if the given buffer starts with an ssh rsa key signature.
  *
- * \return Number of header bytes to be skipped on success, zero if
- * ssh rsa signature was not found.
+ * Returns number of header bytes to be skipped on success, zero if no ssh rsa
+ * signature was found.
  */
-size_t is_ssh_rsa_key(char *data, size_t size)
+static size_t is_ssh_rsa_key(char *data, size_t size)
 {
        char *cp;
 
@@ -45,50 +41,20 @@ size_t is_ssh_rsa_key(char *data, size_t size)
        return cp - data;
 }
 
-/**
- * Read a 4-byte number from a buffer in big-endian format.
- *
- * \param vp The buffer.
- *
- * The byte-order of the buffer is expected to be big-endian, unlike read_u32()
- * of portable_io.h which expects little endian.
- *
- * \return The 32 bit number given by \a vp.
- */
-uint32_t read_ssh_u32(const void *vp)
-{
-       const unsigned char *p = (const unsigned char *)vp;
-       uint32_t v;
-
-       v  = (uint32_t)p[0] << 24;
-       v |= (uint32_t)p[1] << 16;
-       v |= (uint32_t)p[2] << 8;
-       v |= (uint32_t)p[3];
-
-       return v;
-}
-
-/**
- * Sanity checks for the header of an ssh key.
- *
- * \param blob The buffer.
- * \param blen The number of bytes of \a blob.
- *
- * This performs some checks to make sure we really have an ssh key. It also
- * computes the offset in bytes of the start of the key values (modulus,
- * exponent..).
+/*
+ * Perform some sanity checks on the decoded ssh key.
  *
- * \return The number of bytes to skip until the start of the first encoded
- * number (usually 11).
+ * This function returns the size of the header. Usually, the header is 11
+ * bytes long: four bytes for the length field, and the string "ssh-rsa".
  */
-int check_ssh_key_header(const unsigned char *blob, int blen)
+static int check_ssh_key_header(const unsigned char *blob, int blen)
 {
        const unsigned char *p = blob, *end = blob + blen;
        uint32_t rlen;
 
        if (p + 4 > end)
                return -E_SSH_KEY_HEADER;
-       rlen = read_ssh_u32(p);
+       rlen = read_u32_be(p);
        p += 4;
        if (p + rlen < p)
                return -E_SSH_KEY_HEADER;
@@ -103,31 +69,73 @@ int check_ssh_key_header(const unsigned char *blob, int blen)
 }
 
 /**
- * Check existence and permissions of a key file.
+ * Perform sanity checks and base64-decode an ssh-rsa key.
+ *
+ * \param filename The public key file (usually id_rsa.pub).
+ * \param blob Pointer to base64-decoded blob is returned here.
+ * \param decoded_size The size of the decoded blob.
+ *
+ * The memory pointed at by the returned blob pointer has to be freed by the
+ * caller.
+ *
+ * \return On success, the offset in bytes of the start of the key values
+ * (modulus, exponent..). This is the number of bytes to skip from the blob
+ * until the start of the first encoded number. On failure, a negative error
+ * code is returned.
+ *
+ * \sa \ref uudecode().
+ */
+int decode_public_key(const char *filename, unsigned char **blob,
+               size_t *decoded_size)
+{
+       int ret, ret2;
+       void *map;
+       size_t map_size;
+
+       ret = mmap_full_file(filename, O_RDONLY, &map, &map_size, NULL);
+       if (ret < 0)
+               return ret;
+       ret = is_ssh_rsa_key(map, map_size);
+       if (ret == 0) {
+               ret = -E_SSH_PARSE;
+               goto unmap;
+       }
+       ret = uudecode(map + ret, map_size - ret, (char **)blob, decoded_size);
+       if (ret < 0)
+               goto unmap;
+       ret = check_ssh_key_header(*blob, *decoded_size);
+       if (ret < 0)
+               goto unmap;
+unmap:
+       ret2 = para_munmap(map, map_size);
+       if (ret >= 0 && ret2 < 0)
+               ret = ret2;
+       return ret;
+}
+
+/**
+ * Check existence and permissions of a private key file.
  *
  * \param file The path of the key file.
- * \param private_key Whether this is a private key.
  *
- * This checks whether the file exists. If it is a private key, we additionally
- * check that the permissions are restrictive enough. It is considered an error
- * if we own the file and it is readable for others.
+ * This checks whether the file exists and its permissions are restrictive
+ * enough. It is considered an error if we own the file and it is readable for
+ * others.
  *
  * \return Standard.
  */
-int check_key_file(const char *file, bool private_key)
+int check_private_key_file(const char *file)
 {
        struct stat st;
 
        if (stat(file, &st) != 0)
                return -ERRNO_TO_PARA_ERROR(errno);
-       if (!private_key)
-               return 0;
        if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0)
                return -E_KEY_PERM;
        return 1;
 }
 
-void hash_to_asc(unsigned char *hash, char *asc)
+void hash_to_asc(const unsigned char *hash, char *asc)
 {
        int i;
        const char hexchar[] = "0123456789abcdef";
@@ -139,7 +147,7 @@ void hash_to_asc(unsigned char *hash, char *asc)
        asc[2 * HASH_SIZE] = '\0';
 }
 
-int hash_compare(unsigned char *h1, unsigned char *h2)
+int hash_compare(const unsigned char *h1, const unsigned char *h2)
 {
        int i;
 
@@ -151,3 +159,161 @@ int hash_compare(unsigned char *h1, unsigned char *h2)
        }
        return 0;
 }
+
+/**
+ * Check header of an openssh private key and compute bignum offset.
+ *
+ * \param data The base64-decoded key.
+ * \param len The size of the decoded key.
+ *
+ * Several assumptions are made about the key. Most notably, we only support
+ * single unencrypted keys without comments.
+ *
+ * \return The offset at which the first bignum of the private key (the public
+ * exponent n) starts. Negative error code on failure.
+ */
+int find_openssh_bignum_offset(const unsigned char *data, int len)
+{
+       /*
+        * Unencrypted keys without comments always start with the below byte
+        * sequence. See PROTOCOL.key of the openssh package.
+        */
+       static const unsigned char valid_openssh_header[] = {
+               /* string "openssh-key-v1" */
+               0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2d, 0x6b, 0x65,
+                       0x79, 0x2d, 0x76, 0x31,
+               /* length of the cipher name */
+               0x00, 0x00, 0x00, 0x00, 0x04,
+               /* cipher name: "none" */
+               0x6e, 0x6f, 0x6e, 0x65,
+               /* length of the kdfname (only used for encrypted keys) */
+               0x00, 0x00, 0x00, 0x04,
+               /* kdfname: "none" */
+               0x6e, 0x6f, 0x6e, 0x65,
+               /* length of kdfoptions */
+               0x00, 0x00, 0x00, 0x00,
+               /* number of keys */
+               0x00, 0x00, 0x00, 0x01,
+       };
+       uint32_t val;
+       const unsigned char *p, *end = data + len;
+
+       if (len <= sizeof(valid_openssh_header) + 4)
+               return -E_OPENSSH_PARSE;
+       if (memcmp(data, valid_openssh_header, sizeof(valid_openssh_header)))
+               return -E_OPENSSH_PARSE;
+       p = data + sizeof(valid_openssh_header);
+       /* length of public key */
+       val = read_u32_be(p);
+       if (val > end - p - 4)
+               return -E_OPENSSH_PARSE;
+       p += val + 4;
+       /* length of private key */
+       val = read_u32_be(p);
+       if (val > end - p - 4)
+               return -E_OPENSSH_PARSE;
+       p += 4;
+       /* two equal random integers ("checkint") */
+       if (p + 8 > end)
+               return -E_OPENSSH_PARSE;
+       if (read_u32_be(p) != read_u32_be(p + 4))
+               return -E_OPENSSH_PARSE;
+       p += 8;
+       /* length of name of key type "ssh-rsa" */
+       if (p + 11 > end)
+               return -E_OPENSSH_PARSE;
+       if (read_u32_be(p) != 7)
+               return -E_OPENSSH_PARSE;
+       if (memcmp(p + 4, "ssh-rsa", 7))
+               return -E_OPENSSH_PARSE;
+       p += 11;
+       return p - data;
+}
+
+/** Private PEM keys (legacy format) start with this header. */
+#define PRIVATE_PEM_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
+/** Private OPENSSH keys (RFC4716) start with this header. */
+#define PRIVATE_OPENSSH_KEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----"
+/** Private PEM keys (legacy format) end with this footer. */
+#define PRIVATE_PEM_KEY_FOOTER "-----END RSA PRIVATE KEY-----"
+/** Private OPENSSH keys (RFC4716) end with this footer. */
+#define PRIVATE_OPENSSH_KEY_FOOTER "-----END OPENSSH PRIVATE KEY-----"
+
+/**
+ * Decode an openssh-v1 (aka RFC4716) or PEM (aka ASN.1) private key.
+ *
+ * \param key_file The private key file (usually id_rsa).
+ * \param result Pointer to base64-decoded blob is returned here.
+ * \param blob_size The size of the decoded blob.
+ *
+ * This only checks header and footer and base64-decodes the part in between.
+ * No attempt to read the decoded part is made.
+ *
+ * \return Negative on errors, PKT_PEM or PKT_OPENSSH on success, indicating
+ * the type of key.
+ */
+int decode_private_key(const char *key_file, unsigned char **result,
+               size_t *blob_size)
+{
+       int ret, ret2, i, j, key_type;
+       void *map;
+       size_t map_size, key_size;
+       unsigned char *blob = NULL;
+       char *begin, *footer, *key;
+
+       ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
+       if (ret < 0)
+               goto out;
+       ret = -E_KEY_MARKER;
+       if (strncmp(map, PRIVATE_PEM_KEY_HEADER,
+                       strlen(PRIVATE_PEM_KEY_HEADER)) == 0) {
+               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);
+       } else if (strncmp(map, PRIVATE_OPENSSH_KEY_HEADER,
+                       strlen(PRIVATE_OPENSSH_KEY_HEADER)) == 0) {
+               key_type = PKT_OPENSSH;
+               begin = map + strlen(PRIVATE_OPENSSH_KEY_HEADER);
+               footer = strstr(map, PRIVATE_OPENSSH_KEY_FOOTER);
+               PARA_INFO_LOG("detected openssh key %s\n", key_file);
+       } else
+               goto unmap;
+       if (!footer)
+               goto unmap;
+       /* skip whitespace at the beginning */
+       for (; begin < footer; begin++) {
+               if (para_isspace(*begin))
+                       continue;
+               break;
+       }
+       ret = -E_KEY_MARKER;
+       if (begin >= footer)
+               goto unmap;
+
+       key_size = footer - begin;
+       key = para_malloc(key_size + 1);
+       for (i = 0, j = 0; begin + i < footer; i++) {
+               if (para_isspace(begin[i]))
+                       continue;
+               key[j++] = begin[i];
+       }
+       key[j] = '\0';
+       ret = base64_decode(key, j, (char **)&blob, blob_size);
+       free(key);
+       if (ret < 0)
+               goto unmap;
+       ret = key_type;
+unmap:
+       ret2 = para_munmap(map, map_size);
+       if (ret >= 0 && ret2 < 0)
+               ret = ret2;
+       if (ret < 0) {
+               free(blob);
+               blob = NULL;
+       }
+out:
+       *result = blob;
+       return ret;
+}
+
index 20cdc6ae605a99f56f35bf41f0abeff7d508936d..a4e2f3193730d456ce9908eb55888cdb759154e0 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file daemon.c Some helpers for programs that detach from the console. */
 
@@ -34,6 +30,13 @@ struct daemon {
        char *hostname;
        /** Used for colored log messages. */
        char log_colors[NUM_LOGLEVELS][COLOR_MAXLEN];
+       char *old_cwd;
+       /*
+        * If these pointers are non-NULL, the functions are called from
+        * daemon_log() before and after writing each log message.
+        */
+       void (*pre_log_hook)(void);
+       void (*post_log_hook)(void);
 };
 
 static struct daemon the_daemon, *me = &the_daemon;
@@ -121,7 +124,12 @@ void daemon_set_logfile(const char *logfile_name)
 {
        free(me->logfile_name);
        me->logfile_name = NULL;
-       if (logfile_name)
+       if (!logfile_name)
+               return;
+       if (me->old_cwd && logfile_name[0] != '/')
+               me->logfile_name = make_message("%s/%s", me->old_cwd,
+                       logfile_name);
+       else
                me->logfile_name = para_strdup(logfile_name);
 }
 
@@ -138,6 +146,28 @@ void daemon_set_loglevel(const char *loglevel)
        me->loglevel = ret;
 }
 
+/**
+ * Register functions to be called before and after a message is logged.
+ *
+ * \param pre_log_hook Called before the message is logged.
+ * \param post_log_hook Called after the message is logged.
+ *
+ * The purpose of this function is to provide a primitive for multi-threaded
+ * applications to serialize the access to the log facility, preventing
+ * interleaving log messages. This can be achieved by having the pre-log hook
+ * acquire a lock which blocks the other threads on the attempt to log a
+ * message at the same time.  The post-log hook is responsible for releasing
+ * the lock.
+ *
+ * If these hooks are unnecessary, for example because the application is
+ * single-threaded, this function does not need to be called.
+ */
+void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void))
+{
+       me->pre_log_hook = pre_log_hook;
+       me->post_log_hook = post_log_hook;
+}
+
 /**
  * Set one of the daemon config flags.
  *
@@ -201,6 +231,7 @@ int daemonize(bool parent_waits)
        /* become session leader */
        if (setsid() < 0)
                goto err;
+       me->old_cwd = getcwd(NULL, 0);
        if (chdir("/") < 0)
                goto err;
        null = open("/dev/null", O_RDWR);
@@ -238,15 +269,18 @@ void daemon_close_log(void)
  */
 void daemon_open_log_or_die(void)
 {
-       daemon_close_log();
+       FILE *new_log;
+
        if (!me->logfile_name)
                return;
-       me->logfile = fopen(me->logfile_name, "a");
-       if (!me->logfile) {
+       new_log = fopen(me->logfile_name, "a");
+       if (!new_log) {
                PARA_EMERG_LOG("can not open %s: %s\n", me->logfile_name,
                        strerror(errno));
                exit(EXIT_FAILURE);
        }
+       daemon_close_log();
+       me->logfile = new_log;
        /* equivalent to setlinebuf(), but portable */
        setvbuf(me->logfile, NULL, _IOLBF, 0);
 }
@@ -374,8 +408,6 @@ time_t daemon_get_uptime(const struct timeval *current_time)
  * \param current_time See a \ref daemon_get_uptime().
  *
  * \return A dynamically allocated string of the form "days:hours:minutes".
- *
- * \sa server_uptime.
  */
 __malloc char *daemon_get_uptime_str(const struct timeval *current_time)
 {
@@ -405,6 +437,8 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...)
                return;
 
        fp = me->logfile? me->logfile : stderr;
+       if (me->pre_log_hook)
+               me->pre_log_hook();
        color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL;
        if (color)
                fprintf(fp, "%s", color);
@@ -437,4 +471,6 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...)
        va_end(argp);
        if (color)
                fprintf(fp, "%s", COLOR_RESET);
+       if (me->post_log_hook)
+               me->post_log_hook();
 }
index 989678df40e6b29a44ae8fb5491db05841d25bca..b530b0d76b48b673fd7ae7626c48a6dd91ab7751 100644 (file)
--- a/daemon.h
+++ b/daemon.h
@@ -11,6 +11,7 @@ void daemon_set_start_time(void);
 time_t daemon_get_uptime(const struct timeval *current_time);
 __malloc char *daemon_get_uptime_str(const struct timeval *current_time);
 void daemon_set_logfile(const char *logfile_name);
+void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void));
 void daemon_set_flag(unsigned flag);
 void daemon_set_loglevel(const char *loglevel);
 bool daemon_init_colors_or_die(int color_arg, int color_arg_auto,
index 253586e1f7ee4f90867a157757a34a227458047a..639c93fcbf99bc93ddc054ad43d03f30811270ac 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file dccp_recv.c paraslash's dccp receiver */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "recv_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "recv.h"
 #include "string.h"
 #include "net.h"
 #include "fd.h"
 
-#include "dccp_recv.cmdline.h"
-
 static void dccp_recv_close(struct receiver_node *rn)
 {
        if (rn->fd > 0)
@@ -39,25 +34,56 @@ static void dccp_recv_close(struct receiver_node *rn)
        btr_pool_free(rn->btrp);
 }
 
+/* Check whether the host supports the requested 'ccid' arguments. */
+static int dccp_recv_ccid_support_check(const struct lls_parse_result *lpr)
+{
+       uint8_t *ccids;
+       int i, j, ret, nccids;
+       unsigned given = RECV_CMD_OPT_GIVEN(DCCP, CCID, lpr);
+
+       ret = dccp_available_ccids(&ccids);
+       if (ret < 0)
+               return ret;
+       nccids = ret;
+       for (i = 0; i < given; i++) {
+               uint32_t val = lls_uint32_val(i,
+                       RECV_CMD_OPT_RESULT(DCCP, CCID, lpr));
+               for (j = 0; j < nccids && ccids[j] != val; j++)
+                       ;
+               if (j == nccids) {
+                       PARA_ERROR_LOG("'CCID-%u' not supported on this host\n",
+                               val);
+                       return -ERRNO_TO_PARA_ERROR(EINVAL);
+               }
+       }
+       return 1;
+}
+
 static int dccp_recv_open(struct receiver_node *rn)
 {
-       struct dccp_recv_args_info *conf = rn->conf;
+       struct lls_parse_result *lpr = rn->lpr;
        struct flowopts *fo = NULL;
        uint8_t *ccids = NULL;
        int fd, ret, i;
+       const struct lls_opt_result *r_c = RECV_CMD_OPT_RESULT(DCCP, CCID, lpr);
+       const char *host = RECV_CMD_OPT_STRING_VAL(DCCP, HOST, lpr);
+       uint32_t port = RECV_CMD_OPT_UINT32_VAL(DCCP, PORT, lpr);
+       unsigned given;
 
+       ret = dccp_recv_ccid_support_check(lpr);
+       if (ret < 0)
+               return ret;
        /* Copy CCID preference list (u8 array required) */
-       if (conf->ccid_given) {
-               ccids = para_malloc(conf->ccid_given);
-               fo    = flowopt_new();
-
-               for (i = 0; i < conf->ccid_given; i++)
-                       ccids[i] = conf->ccid_arg[i];
-
+       given = lls_opt_given(r_c);
+       if (given) {
+               ccids = para_malloc(given);
+               fo = flowopt_new();
+               for (i = 0; i < given; i++)
+                       ccids[i] = lls_int32_val(i, r_c);
                OPT_ADD(fo, SOL_DCCP, DCCP_SOCKOPT_CCID, ccids, i);
        }
 
-       fd = makesock(IPPROTO_DCCP, 0, conf->host_arg, conf->port_arg, fo);
+       fd = makesock(IPPROTO_DCCP, 0, host, port, fo);
        flowopt_cleanup(fo);
        free(ccids);
        if (fd < 0)
@@ -83,42 +109,6 @@ err:
        return ret;
 }
 
-/**
- * Check whether the host supports the requested 'ccid' arguments.
- * \param conf DCCP receiver arguments.
- * \return True if all CCIDs requested in \a conf are supported.
- */
-static bool dccp_recv_ccid_support_check(struct dccp_recv_args_info *conf)
-{
-       uint8_t *ccids;
-       int i, j, nccids;
-
-       nccids = dccp_available_ccids(&ccids);
-       if (nccids <= 0)
-               return false;
-
-       for (i = 0; i < conf->ccid_given; i++) {
-               for (j = 0; j < nccids && ccids[j] != conf->ccid_arg[i]; j++)
-                       ;
-               if (j == nccids) {
-                       PARA_ERROR_LOG("'CCID-%d' not supported on this host.\n",
-                                       conf->ccid_arg[i]);
-                       return false;
-               }
-       }
-       return true;
-}
-
-static void *dccp_recv_parse_config(int argc, char **argv)
-{
-       struct dccp_recv_args_info *tmp = para_calloc(sizeof(*tmp));
-
-       dccp_recv_cmdline_parser(argc, argv, tmp);
-       if (!dccp_recv_ccid_support_check(tmp))
-               exit(EXIT_FAILURE);
-       return tmp;
-}
-
 static void dccp_recv_pre_select(struct sched *s, void *context)
 {
        struct receiver_node *rn = context;
@@ -161,30 +151,9 @@ out:
        return ret;
 }
 
-static void dccp_recv_free_config(void *conf)
-{
-       dccp_recv_cmdline_parser_free(conf);
-       free(conf);
-}
-
-/**
- * The init function of the dccp receiver.
- *
- * \param r Pointer to the receiver struct to initialize.
- *
- * Initialize all function pointers of \a r.
- */
-void dccp_recv_init(struct receiver *r)
-{
-       struct dccp_recv_args_info dummy;
-
-       dccp_recv_cmdline_parser_init(&dummy);
-       r->open = dccp_recv_open;
-       r->close = dccp_recv_close;
-       r->pre_select = dccp_recv_pre_select;
-       r->post_select = dccp_recv_post_select;
-       r->parse_config = dccp_recv_parse_config;
-       r->free_config = dccp_recv_free_config;
-       r->help = (struct ggo_help)DEFINE_GGO_HELP(dccp_recv);
-       dccp_recv_cmdline_parser_free(&dummy);
-}
+const struct receiver lsg_recv_cmd_com_dccp_user_data = {
+       .open = dccp_recv_open,
+       .close = dccp_recv_close,
+       .pre_select = dccp_recv_pre_select,
+       .post_select = dccp_recv_post_select,
+};
index 92dd933347d2c48d857355da34c13673da54968b..bca7ad6781e29d2bb1bea686d3971e02ede59909 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file dccp_send.c Paraslash's dccp sender. */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "server.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "string.h"
 #include "afh.h"
-#include "server.h"
 #include "net.h"
+#include "server.h"
 #include "list.h"
 #include "send.h"
 #include "sched.h"
 #include "vss.h"
 #include "fd.h"
-#include "close_on_fork.h"
-#include "chunk_queue.h"
-#include "server.cmdline.h"
-#include "acl.h"
 
 static struct sender_status dccp_sender_status, *dss = &dccp_sender_status;
 
@@ -45,8 +39,11 @@ struct dccp_fec_client {
 static void dccp_pre_select(int *max_fileno, fd_set *rfds,
                __a_unused fd_set *wfds)
 {
-       if (dss->listen_fd >= 0)
-               para_fd_set(dss->listen_fd, rfds, max_fileno);
+       unsigned n;
+
+       FOR_EACH_LISTEN_FD(n, dss)
+               if (dss->listen_fds[n] >= 0)
+                       para_fd_set(dss->listen_fds[n], rfds, max_fileno);
 }
 
 /**
@@ -86,11 +83,19 @@ static void dccp_shutdown_clients(void)
                dccp_shutdown_client(sc);
 }
 
+static void dccp_shutdown(void)
+{
+       dccp_shutdown_clients();
+       generic_acl_deplete(&dss->acl);
+       free_sender_status(dss);
+}
+
 /** * Obtain current MPS according to RFC 4340, sec. 14. */
 static int dccp_init_fec(struct sender_client *sc)
 {
        int mps, ret;
        socklen_t ml = sizeof(mps);
+       uint32_t mss; /* max slize size */
 
        /* If call fails, return some sensible minimum value */
        ret = getsockopt(sc->fd, SOL_DCCP, DCCP_SOCKOPT_GET_CUR_MPS, &mps, &ml);
@@ -100,8 +105,9 @@ static int dccp_init_fec(struct sender_client *sc)
        }
        PARA_INFO_LOG("current MPS = %d bytes\n", mps);
        assert(mps > 0);
-       if (conf.dccp_max_slice_size_arg > 0)
-               mps = PARA_MIN(mps, conf.dccp_max_slice_size_arg);
+       mss = OPT_UINT32_VAL(DCCP_MAX_SLICE_SIZE);
+       if (mss > 0 && mss <= INT_MAX)
+               mps = PARA_MIN(mps, (int)mss);
        return mps;
 }
 
@@ -118,6 +124,7 @@ static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds)
        struct sender_client *sc;
        struct dccp_fec_client *dfc;
        int tx_ccid;
+       uint32_t k, n;
 
        sc = accept_sender_client(dss, rfds);
        if (!sc)
@@ -134,7 +141,7 @@ static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds)
        /*
         * Bypass unused CCID paths: the sender does not receive application data
         * from the client; by shutting down this unused communication path we can
-        * reduce processing costs a bit. See analogous comment in dccp_recv.c.
+        * reduce processing costs a bit. See analogous comment in \ref dccp_recv.c.
         */
        if (shutdown(sc->fd, SHUT_RD) < 0) {
                PARA_WARNING_LOG("%s\n", strerror(errno));
@@ -143,8 +150,16 @@ static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds)
        }
        dfc = para_calloc(sizeof(*dfc));
        sc->private_data = dfc;
-       dfc->fcp.data_slices_per_group  = conf.dccp_data_slices_per_group_arg;
-       dfc->fcp.slices_per_group       = conf.dccp_slices_per_group_arg;
+       k = OPT_UINT32_VAL(DCCP_DATA_SLICES_PER_GROUP);
+       n = OPT_UINT32_VAL(DCCP_SLICES_PER_GROUP);
+       if (k == 0 || n == 0 || k >= n) {
+               PARA_WARNING_LOG("invalid FEC parameters, using defaults\n");
+               dfc->fcp.data_slices_per_group = 3;
+               dfc->fcp.slices_per_group = 4;
+       } else {
+               dfc->fcp.data_slices_per_group = k;
+               dfc->fcp.slices_per_group = n;
+       }
        dfc->fcp.init_fec               = dccp_init_fec;
        dfc->fcp.send_fec               = dccp_send_fec;
        dfc->fcp.need_periodic_header   = false;
@@ -153,7 +168,8 @@ static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds)
 
 static int dccp_com_on(__a_unused struct sender_command_data *scd)
 {
-       return generic_com_on(dss, IPPROTO_DCCP);
+       generic_com_on(dss, IPPROTO_DCCP);
+       return 1;
 }
 
 static int dccp_com_off(__a_unused struct sender_command_data *scd)
@@ -206,45 +222,42 @@ static char *dccp_status(void)
        return result;
 }
 
-/**
- * The init function of the dccp sender.
- *
- * \param s pointer to the dccp sender struct.
- *
- * It initializes all function pointers of \a s and starts
- * listening on the given port.
+/*
+ * Initialize the client list and the access control list and listen on the
+ * dccp port.
  */
-void dccp_send_init(struct sender *s)
+static void dccp_send_init(void)
 {
-       int ret, k, n;
-
-       s->status = dccp_status;
-       s->send = NULL;
-       s->pre_select = dccp_pre_select;
-       s->post_select = dccp_post_select;
-       s->shutdown_clients = dccp_shutdown_clients;
-       s->resolve_target = NULL;
-       s->help = generic_sender_help;
-       s->client_cmds[SENDER_on] = dccp_com_on;
-       s->client_cmds[SENDER_off] = dccp_com_off;
-       s->client_cmds[SENDER_deny] = dccp_com_deny;
-       s->client_cmds[SENDER_allow] = dccp_com_allow;
-       s->client_cmds[SENDER_add] = NULL;
-       s->client_cmds[SENDER_delete] = NULL;
-
-       k = conf.dccp_data_slices_per_group_arg;
-       n = conf.dccp_slices_per_group_arg;
-
-       if (k <= 0 || n <= 0 || k >= n) {
-               PARA_WARNING_LOG("invalid FEC parameters, using defaults\n");
-               conf.dccp_data_slices_per_group_arg = 3;
-               conf.dccp_slices_per_group_arg = 4;
-       }
-
-       init_sender_status(dss, conf.dccp_access_arg, conf.dccp_access_given,
-               conf.dccp_port_arg, conf.dccp_max_clients_arg,
-               conf.dccp_default_deny_given);
-       ret = generic_com_on(dss, IPPROTO_DCCP);
-       if (ret < 0)
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       init_sender_status(dss, OPT_RESULT(DCCP_ACCESS),
+               OPT_RESULT(DCCP_LISTEN_ADDRESS),
+               OPT_UINT32_VAL(DCCP_PORT), OPT_UINT32_VAL(DCCP_MAX_CLIENTS),
+               OPT_GIVEN(DCCP_DEFAULT_DENY));
+       if (OPT_GIVEN(DCCP_NO_AUTOSTART))
+               return;
+       generic_com_on(dss, IPPROTO_DCCP);
 }
+
+/**
+ * The DCCP sender.
+ *
+ * This sender offers congestion control not available in plain TCP. Most
+ * methods of the sender structure are implemented as simple wrappers for the
+ * generic functions defined in \ref send_common.c. Like UDP streams, DCCP
+ * streams are sent FEC-encoded.
+ */
+const struct sender dccp_sender = {
+       .name = "dccp",
+       .init = dccp_send_init,
+       .shutdown = dccp_shutdown,
+       .pre_select = dccp_pre_select,
+       .post_select = dccp_post_select,
+       .shutdown_clients = dccp_shutdown_clients,
+       .client_cmds = {
+               [SENDER_on] = dccp_com_on,
+               [SENDER_off] = dccp_com_off,
+               [SENDER_deny] = dccp_com_deny,
+               [SENDER_allow] = dccp_com_allow,
+       },
+       .help = generic_sender_help,
+       .status = dccp_status,
+};
diff --git a/error.h b/error.h
index 97d678176ef4b49a9892fcf67a06fc05ec902a7d..fe44ff5c50487744881ea1d21d1007528a2ecd70 100644 (file)
--- a/error.h
+++ b/error.h
@@ -1,25 +1,18 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file error.h List of error codes and messages. */
 
 /** Codes and messages. */
 #define PARA_ERRORS \
        PARA_ERROR(SUCCESS, "success"), \
-       PARA_ERROR(AAC_AFH_INIT, "failed to init aac decoder"), \
        PARA_ERROR(AACDEC_INIT, "failed to init aac decoder"), \
        PARA_ERROR(AAC_DECODE, "aac decode error"), \
        PARA_ERROR(ACL_PERM, "access denied by acl"), \
-       PARA_ERROR(ADD_CALLBACK, "can not add callback"), \
        PARA_ERROR(ADDRESS_LOOKUP, "can not resolve requested address"),\
        PARA_ERROR(AFH_RECV_BAD_FILENAME, "no file name given"), \
        PARA_ERROR(AFS_SHORT_READ, "short read from afs socket"), \
        PARA_ERROR(AFS_SIGNAL, "afs caught deadly signal"), \
        PARA_ERROR(AFS_SOCKET, "afs socket not writable"), \
-       PARA_ERROR(AFS_SYNTAX, "afs syntax error"), \
        PARA_ERROR(AFT_SYNTAX, "audio file table syntax error"), \
        PARA_ERROR(ALSA, "alsa error"), \
        PARA_ERROR(ALSA_MIX_GET_VAL, "could not read control element state"), \
@@ -31,7 +24,6 @@
        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_BAD_SAMPLE_FORMAT, "ao: unsigned sample formats not supported"), \
        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(BAD_BAND, "invalid or unexpected band designator"), \
        PARA_ERROR(BAD_CHANNEL_COUNT, "channel count not supported"), \
        PARA_ERROR(BAD_CHANNEL, "invalid channel"), \
-       PARA_ERROR(BAD_CMD, "invalid command"), \
-       PARA_ERROR(BAD_CONFIG, "syntax error in config file"), \
        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_FILTER_OPTIONS, "invalid filter option given"), \
        PARA_ERROR(BAD_LL, "invalid loglevel"), \
        PARA_ERROR(BAD_PATH, "invalid path"), \
-       PARA_ERROR(BAD_PLAY_CMD, "invalid command"), \
        PARA_ERROR(BAD_PRIVATE_KEY, "invalid private key"), \
        PARA_ERROR(BAD_SAMPLE_FORMAT, "sample format not supported"), \
        PARA_ERROR(BAD_SAMPLERATE, "sample rate not supported"), \
@@ -81,6 +69,7 @@
        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"), \
        PARA_ERROR(CHMOD, "failed to set socket mode"), \
        PARA_ERROR(CLIENT_SYNTAX, "syntax error"), \
        PARA_ERROR(CLIENT_WRITE, "client write error"), \
        PARA_ERROR(CREATE_OPUS_DECODER, "could not create opus decoder"), \
        PARA_ERROR(DCCP_OVERRUN, "dccp output buffer buffer overrun"), \
        PARA_ERROR(DECRYPT, "decrypt error"), \
-       PARA_ERROR(DEFAULT_COMP, "can not find default audio output component"), \
        PARA_ERROR(DUMMY_ROW, "attempted to access blob dummy object"), \
        PARA_ERROR(DUP_PIPE, "exec error: can not create pipe"), \
        PARA_ERROR(EMPTY, "file is empty"), \
        PARA_ERROR(ENCRYPT, "encrypt error"), \
        PARA_ERROR(EOF, "end of file"), \
-       PARA_ERROR(ESDS, "did not find esds atom"), \
+       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(ID3_SETENCODING, "could not set id3 text encoding field"), \
        PARA_ERROR(ID3_SETSTRING, "could not set id3 string field"), \
        PARA_ERROR(INCOHERENT_BLOCK_LEN, "incoherent block length"), \
-       PARA_ERROR(INVALID_AUDIOD_CMD, "invalid command"), \
        PARA_ERROR(KEY_MARKER, "invalid/missing key header or footer"), \
        PARA_ERROR(KEY_PERM, "unprotected private key"), \
        PARA_ERROR(LIBSAMPLERATE, "secret rabbit code error"), \
        PARA_ERROR(MAX_CLIENTS, "maximal number of clients exceeded"), \
        PARA_ERROR(MISSING_COLON, "syntax error: missing colon"), \
        PARA_ERROR(MOOD_SYNTAX, "mood syntax error"), \
+       PARA_ERROR(MOOD_PARSE, "mood parse error"), \
        PARA_ERROR(MP3DEC_CORRUPT, "too many corrupt frames"), \
        PARA_ERROR(MP3DEC_EOF, "mp3dec: end of file"), \
        PARA_ERROR(MP3_INFO, "could not read mp3 info"), \
-       PARA_ERROR(MP4ASC, "audio spec config error"), \
-       PARA_ERROR(MP4V2, "mp4v2 library error"), \
-       PARA_ERROR(MPI_PRINT, "could not convert multi-precision integer"), \
+       PARA_ERROR(MP4FF_BAD_CHANNEL_COUNT, "mp4ff: invalid number of channels"), \
+       PARA_ERROR(MP4FF_BAD_SAMPLE, "mp4ff: invalid sample number"), \
+       PARA_ERROR(MP4FF_BAD_SAMPLERATE, "mp4ff: invalid sample rate"), \
+       PARA_ERROR(MP4FF_BAD_SAMPLE_COUNT, "mp4ff: invalid number of samples"), \
+       PARA_ERROR(MP4FF_META_READ, "mp4ff: could not read mp4 metadata"), \
+       PARA_ERROR(MP4FF_META_WRITE, "mp4ff: could not update mp4 metadata"), \
+       PARA_ERROR(MP4FF_OPEN, "mp4ff: open failed"), \
+       PARA_ERROR(MP4FF_TRACK, "mp4ff: no audio track"), \
        PARA_ERROR(MPI_SCAN, "could not scan multi-precision integer"), \
        PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \
        PARA_ERROR(NO_AFHI, "audio format handler info required"), \
        PARA_ERROR(NO_ATTRIBUTES, "no attributes defined yet"), \
        PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \
-       PARA_ERROR(NO_CONFIG, "config file not found"), \
        PARA_ERROR(NOFD, "did not receive open fd from afs"), \
-       PARA_ERROR(NO_FILTERS, "at least one filter must be given"), \
        PARA_ERROR(NO_MATCH, "no matches"), \
        PARA_ERROR(NO_MOOD, "no mood available"), \
        PARA_ERROR(NO_MORE_SLOTS, "no more empty slots"), \
        PARA_ERROR(NOT_PLAYING, "not playing"), \
        PARA_ERROR(NO_VALID_FILES, "no valid file found in playlist"), \
        PARA_ERROR(NO_WMA, "asf/wma format not recognized"), \
-       PARA_ERROR(OEAP, "error during oeap (un)padding"), \
        PARA_ERROR(OGGDEC_BADHEADER, "invalid vorbis bitstream header"), \
        PARA_ERROR(OGGDEC_BADLINK, "invalid stream section or requested link corrupt"), \
        PARA_ERROR(OGGDEC_FAULT, "bug or heap/stack corruption"), \
        PARA_ERROR(OGG_PACKET_IN, "ogg_stream_packetin() failed"), \
        PARA_ERROR(OGG_STREAM_FLUSH, "ogg_stream_flush() failed"), \
        PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \
-       PARA_ERROR(OPEN_COMP, "OpenAComponent() error"), \
+       PARA_ERROR(OPENSSH_PARSE, "could not parse openssh private key"), \
        PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \
        PARA_ERROR(OPUS_DECODE, "opus decode error"), \
        PARA_ERROR(OPUS_HEADER, "invalid opus header"), \
        PARA_ERROR(PERM, "permission denied"), \
        PARA_ERROR(PLAYLIST_EMPTY, "attempted to load empty playlist"), \
        PARA_ERROR(PLAYLIST_LOADED, ""), /* not really an error */ \
-       PARA_ERROR(PLAY_SYNTAX, "para_play: syntax error"), \
        PARA_ERROR(PREBUFFER_SUCCESS, "prebuffering complete"), \
        PARA_ERROR(PRIVATE_KEY, "can not read private key"), \
-       PARA_ERROR(PUBLIC_KEY, "can not read public 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(RECV_SYNTAX, "recv syntax error"), \
        PARA_ERROR(REGEX, "regular expression error"), \
        PARA_ERROR(RESAMPLE_EOF, "resample filter: end of file"), \
        PARA_ERROR(RSA, "RSA error"), \
+       PARA_ERROR(RSA_DECODE, "RSA decoding error"), \
        PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \
        PARA_ERROR(SCM_CREDENTIALS, "did not receive SCM credentials"), \
        PARA_ERROR(SENDER_CMD, "command not supported by this sender"), \
        PARA_ERROR(SSH_PARSE, "could not parse ssh public key"), \
        PARA_ERROR(STAT_ITEM_PARSE, "failed to parse status item"), \
        PARA_ERROR(STATUS_TIMEOUT, "status item timeout"), \
-       PARA_ERROR(STCO, "did not find stco atom"), \
-       PARA_ERROR(STREAM_FORMAT, "could not set stream format"), \
        PARA_ERROR(STREAM_PACKETIN, "ogg stream packet-in error"), \
        PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error"), \
        PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error"), \
        PARA_ERROR(STREAM_PAGEOUT, "ogg stream page-out error"), \
        PARA_ERROR(STRFTIME, "strftime() failed"), \
-       PARA_ERROR(STSZ, "did not find stcz atom"), \
        PARA_ERROR(SYNC_COMPLETE, "all buddies in sync"), \
        PARA_ERROR(SYNC_LISTEN_FD, "no fd to listen on"), \
        PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \
        PARA_ERROR(TARGET_EXISTS, "requested target is already present"),\
        PARA_ERROR(TARGET_NOT_FOUND, "requested target not found"), \
        PARA_ERROR(TASK_STARTED, "task started"), \
+       PARA_ERROR(DEADLY_SIGNAL, "termination request by signal"), \
        PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \
        PARA_ERROR(UCRED_PERM, "permission denied"), \
        PARA_ERROR(UDP_OVERRUN, "output buffer overrun"), \
-       PARA_ERROR(UNIT_INIT, "AudioUnitInitialize() error"), \
-       PARA_ERROR(UNIT_START, "AudioUnitStart() error"), \
        PARA_ERROR(UNKNOWN_STAT_ITEM, "status item not recognized"), \
        PARA_ERROR(UNSUPPORTED_AUDIO_FORMAT, "given audio format not supported"), \
        PARA_ERROR(UNSUPPORTED_FILTER, "given filter not supported"), \
 enum para_error_codes {PARA_ERRORS};
 #undef PARA_ERROR
 #define PARA_ERROR(err, msg) msg
-/** Array of error strings. */
+/** All .c files need the declararation of the array of error strings. */
 extern const char * const para_errlist[];
+/** Exactly one .c file per executable must define the array. */
 #define DEFINE_PARA_ERRLIST const char * const para_errlist[] = {PARA_ERRORS}
 
 /**
@@ -283,24 +268,32 @@ extern const char * const para_errlist[];
  */
 #define SYSTEM_ERROR_BIT 30
 
-/**
- * Like the SYSTEM_ERROR_BIT, but indicates an error from the osl library.
- */
+/** Like SYSTEM_ERROR_BIT, but for errors from the osl library. */
 #define OSL_ERROR_BIT 29
 
+/** Like SYSTEM_ERROR_BIT, but for errors from the lopsub library. */
+#define LLS_ERROR_BIT 28
+
 /** Check whether the system error bit is set. */
 #define IS_SYSTEM_ERROR(num) (!!((num) & (1 << SYSTEM_ERROR_BIT)))
 
 /** Check whether the osl error bit is set. */
 #define IS_OSL_ERROR(num) (!!((num) & (1 << OSL_ERROR_BIT)))
 
+/** Check whether the lopsub error bit is set. */
+#define IS_LLS_ERROR(num) (!!((num) & (1 << LLS_ERROR_BIT)))
+
 /** Set the system error bit for the given number. */
 #define ERRNO_TO_PARA_ERROR(num) ((num) | (1 << SYSTEM_ERROR_BIT))
 
 /** Set the osl error bit for the given number. */
 #define OSL_ERRNO_TO_PARA_ERROR(num) ((num) | (1 << OSL_ERROR_BIT))
 
+/** Set the lopsub error bit for the error code. */
+#define LLS_ERRNO_TO_PARA_ERROR(num) ((num) | (1 << LLS_ERROR_BIT))
+
 static const char *weak_osl_strerror(int) __attribute__ ((weakref("osl_strerror")));
+static const char *weak_lls_strerror(int) __attribute__ ((weakref("lls_strerror")));
 /**
  * Paraslash's version of strerror(3).
  *
@@ -315,6 +308,10 @@ _static_inline_ const char *para_strerror(int num)
                assert(weak_osl_strerror);
                return weak_osl_strerror(num & ~(1U << OSL_ERROR_BIT));
        }
+       if (IS_LLS_ERROR(num)) {
+               assert(weak_lls_strerror);
+               return weak_lls_strerror(num & ~(1U << LLS_ERROR_BIT));
+       }
        if (IS_SYSTEM_ERROR(num))
                return strerror(num & ~(1U << SYSTEM_ERROR_BIT));
        return para_errlist[num];
@@ -337,3 +334,16 @@ _static_inline_ int osl(int ret)
                return ret;
        return -OSL_ERRNO_TO_PARA_ERROR(-ret);
 }
+
+/**
+ * Wrapper for lopsub library calls.
+ *
+ * \param ret See \ref osl().
+ * \return See \ref osl().
+ */
+_static_inline_ int lls(int ret)
+{
+       if (ret >= 0)
+               return ret;
+       return -LLS_ERRNO_TO_PARA_ERROR(-ret);
+}
diff --git a/exec.c b/exec.c
index a2348ba392ea684ec02e9add6266f6189fa09267..85dbaf9474aa286add74cd761f09d30dc74ad805 100644 (file)
--- a/exec.c
+++ b/exec.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2003 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2003 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file exec.c Helper functions for spawning new processes. */
 
diff --git a/fade.c b/fade.c
deleted file mode 100644 (file)
index 67dc4d5..0000000
--- a/fade.c
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file fade.c A volume fader and alarm clock for OSS. */
-
-#include <regex.h>
-
-#include "fade.cmdline.h"
-#include "para.h"
-#include "fd.h"
-#include "string.h"
-#include "mix.h"
-#include "error.h"
-#include "ggo.h"
-#include "version.h"
-
-/** Array of error strings. */
-DEFINE_PARA_ERRLIST;
-
-static struct fade_args_info conf;
-
-enum mixer_id {MIXER_ENUM};
-static char *mixer_name[] = {MIXER_NAMES};
-DECLARE_MIXER_INITS;
-static struct mixer supported_mixer[] = {MIXER_ARRAY};
-#define FOR_EACH_MIXER(i) for ((i) = 0; (i) < NUM_SUPPORTED_MIXERS; (i)++)
-
-static int loglevel;
-static __printf_2_3 void date_log(int ll, const char *fmt, ...)
-{
-       va_list argp;
-       time_t t1;
-       struct tm *tm;
-
-       if (ll < loglevel)
-               return;
-       time(&t1);
-       tm = localtime(&t1);
-       fprintf(stderr, "%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec);
-       va_start(argp, fmt);
-       vprintf(fmt, argp);
-       va_end(argp);
-}
-__printf_2_3 void (*para_log)(int, const char*, ...) = date_log;
-
-static int set_channel(struct mixer *m, struct mixer_handle *h, const char *channel)
-{
-
-       PARA_NOTICE_LOG("using %s mixer channel\n", channel?
-               channel : "default");
-       return m->set_channel(h, channel);
-}
-
-/* Fade to new volume in fade_time seconds. */
-static int fade(struct mixer *m, struct mixer_handle *h, int new_vol, int fade_time)
-{
-       int vol, diff, incr, ret;
-       unsigned secs;
-       struct timespec ts;
-       unsigned long long tmp, tmp2; /* Careful with that axe, Eugene! */
-
-       if (fade_time <= 0)
-               return m->set(h, new_vol);
-       secs = fade_time;
-       ret = m->get(h);
-       if (ret < 0)
-               goto out;
-       vol = ret;
-       PARA_NOTICE_LOG("fading %s from %d to %d in %u seconds\n",
-               conf.mixer_channel_arg, vol, new_vol, secs);
-       diff = new_vol - vol;
-       if (!diff) {
-               sleep(secs);
-               goto out;
-       }
-       incr = diff > 0? 1: -1;
-       diff = diff > 0? diff: -diff;
-       tmp = secs * 1000 / diff;
-       tmp2 = tmp % 1000;
-       while ((new_vol - vol) * incr > 0) {
-               ts.tv_nsec = tmp2 * 1000000; /* really nec ?*/
-               ts.tv_sec = tmp / 1000; /* really nec ?*/
-               //printf("ts.tv_sec: %i\n", ts.tv_nsec);
-               vol += incr;
-               ret = m->set(h, vol);
-               if (ret < 0)
-                       goto out;
-               //printf("vol = %i\n", vol);
-               nanosleep(&ts, NULL);
-       }
-out:
-       return ret;
-}
-
-static void client_cmd(const char *cmd)
-{
-       int ret, status, fds[3] = {0, 0, 0};
-       pid_t pid;
-       char *cmdline = make_message(BINDIR "/para_client %s", cmd);
-
-       PARA_NOTICE_LOG("%s\n", cmdline);
-       ret = para_exec_cmdline_pid(&pid, cmdline, fds);
-       free(cmdline);
-       if (ret < 0) {
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-               goto fail;
-       }
-       do
-               pid = waitpid(pid, &status, 0);
-       while (pid == -1 && errno == EINTR);
-       if (pid < 0) {
-               PARA_ERROR_LOG("%s\n", strerror(errno));
-               goto fail;
-       }
-       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
-               goto fail;
-       return;
-fail:
-       PARA_EMERG_LOG("command \"%s\" failed\n", cmd);
-       exit(EXIT_FAILURE);
-}
-
-static void change_afs_mode(char *afs_mode)
-{
-       char *cmd;
-
-       client_cmd("stop");
-       if (!afs_mode)
-               return;
-       cmd = make_message("select %s", afs_mode);
-       client_cmd(cmd);
-       free(cmd);
-}
-
-static int set_initial_volume(struct mixer *m, struct mixer_handle *h)
-{
-       int i, ret;
-
-       for (i = 0; i < conf.ivol_given; i++) {
-               char *p, *ch, *arg = para_strdup(conf.ivol_arg[i]);
-               int32_t iv;
-               p = strchr(arg, ':');
-               if (p) {
-                       *p = '\0';
-                       p++;
-                       ch = arg;
-               } else {
-                       p = arg;
-                       ch = NULL;
-               }
-               ret = para_atoi32(p, &iv);
-               if (ret < 0) {
-                       free(arg);
-                       return ret;
-               }
-               ret = set_channel(m, h, ch);
-               if (!ch)
-                       ch = "default";
-               if (ret < 0) {
-                       PARA_WARNING_LOG("ignoring channel %s\n", ch);
-                       ret = 0;
-               } else {
-                       PARA_INFO_LOG("initial volume %s: %d\n", ch, iv);
-                       ret = m->set(h, iv);
-               }
-               free(arg);
-               if (ret < 0)
-                       return ret;
-       }
-       return 1;
-}
-
-static int sweet_dreams(struct mixer *m, struct mixer_handle *h)
-{
-       time_t t1, wake_time_epoch;
-       unsigned int delay;
-       struct tm *tm;
-       int ret, min = conf.wake_min_arg;
-       char *fo_mood = conf.fo_mood_arg;
-       char *fi_mood = conf.fi_mood_arg;
-       char *sleep_mood = conf.sleep_mood_arg;
-       int fit = conf.fi_time_arg;
-       int fot = conf.fo_time_arg;
-       int fiv = conf.fi_vol_arg;
-       int fov = conf.fo_vol_arg;
-
-       /* calculate wake time */
-       time(&t1);
-       if (conf.wake_hour_given) {
-               int hour = conf.wake_hour_arg;
-               tm = localtime(&t1);
-               if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) {
-                       t1 += 86400; /* wake time is tomorrow */
-                       tm = localtime(&t1);
-               }
-               tm->tm_hour = hour;
-               tm->tm_min = min;
-               tm->tm_sec = 0;
-       } else {
-               t1 += 9 * 60 * 60; /* nine hours from now */
-               PARA_INFO_LOG("default wake time: %lu\n", (long unsigned)t1);
-               tm = localtime(&t1);
-       }
-       wake_time_epoch = mktime(tm);
-       PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min);
-       client_cmd("stop");
-       sleep(1);
-       if (fot) {
-               ret = set_initial_volume(m, h);
-               if (ret < 0)
-                       return ret;
-               change_afs_mode(fo_mood);
-               client_cmd("play");
-               ret = set_channel(m, h, conf.mixer_channel_arg);
-               if (ret < 0)
-                       return ret;
-               ret = fade(m, h, fov, fot);
-               if (ret < 0)
-                       return ret;
-       } else {
-               ret = m->set(h, fov);
-               if (ret < 0)
-                       return ret;
-       }
-       if (conf.sleep_mood_given) {
-               change_afs_mode(sleep_mood);
-               client_cmd("play");
-       } else
-               client_cmd("stop");
-       if (!fit)
-               return 1;
-       change_afs_mode(fi_mood);
-       for (;;) {
-               time(&t1);
-               if (wake_time_epoch <= t1 + fit)
-                       break;
-               delay = wake_time_epoch - t1 - fit;
-               PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n",
-                       delay, delay / 3600,
-                       (delay % 3600) / 60);
-               sleep(delay);
-       }
-       client_cmd("play");
-       ret = fade(m, h, fiv, fit);
-       PARA_INFO_LOG("fade complete, returning\n");
-       return ret;
-}
-
-static int snooze(struct mixer *m, struct mixer_handle *h)
-{
-       int ret, val;
-
-       if (conf.so_time_arg <= 0)
-               return 1;
-       ret = m->get(h);
-       if (ret < 0)
-               return ret;
-       val = ret;
-       if (val < conf.so_vol_arg)
-               ret = m->set(h, conf.so_vol_arg);
-       else
-               ret = fade(m, h, conf.so_vol_arg, conf.so_time_arg);
-       if (ret < 0)
-               return ret;
-       client_cmd("pause");
-       PARA_NOTICE_LOG("%d seconds snooze time...\n", conf.snooze_time_arg);
-       sleep(conf.snooze_time_arg);
-       client_cmd("play");
-       return fade(m, h, conf.si_vol_arg, conf.si_time_arg);
-}
-
-static int configfile_exists(void)
-{
-       static char *config_file;
-
-       if (!conf.config_file_given) {
-               char *home = para_homedir();
-               free(config_file);
-               config_file = make_message("%s/.paraslash/fade.conf", home);
-               free(home);
-               conf.config_file_arg = config_file;
-       }
-       return file_exists(conf.config_file_arg);
-}
-
-static void init_mixers(void)
-{
-       int i;
-
-       FOR_EACH_MIXER(i) {
-               struct mixer *m = &supported_mixer[i];
-               PARA_DEBUG_LOG("initializing mixer API #%d (%s)\n",
-                       i, mixer_name[i]);
-               m->init(m);
-       }
-}
-
-static int set_val(struct mixer *m, struct mixer_handle *h)
-{
-       return m->set(h, conf.val_arg);
-}
-
-static struct mixer *get_mixer_or_die(void)
-{
-       int i;
-
-       if (!conf.mixer_api_given)
-               i = DEFAULT_MIXER;
-       else
-               FOR_EACH_MIXER(i)
-                       if (!strcmp(mixer_name[i], conf.mixer_api_arg))
-                               break;
-       if (i < NUM_SUPPORTED_MIXERS) {
-               PARA_NOTICE_LOG("using %s mixer API\n", mixer_name[i]);
-               return supported_mixer + i;
-       }
-       printf("available mixer APIs: ");
-       FOR_EACH_MIXER(i) {
-               int d = (i == DEFAULT_MIXER);
-               printf("%s%s%s ", d? "[" : "", mixer_name[i], d? "]" : "");
-       }
-       printf("\n");
-       exit(EXIT_FAILURE);
-}
-
-__noreturn static void print_help_and_die(void)
-{
-       struct ggo_help h = DEFINE_GGO_HELP(fade);
-       bool d = conf.detailed_help_given;
-
-       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
-       exit(0);
-}
-
-/**
- * The main function of para_fade.
- *
- * The executable is linked with the alsa or the oss mixer API, or both. It has
- * a custom log function which prefixes log messages with the current date.
- *
- * \param argc Argument counter.
- * \param argv Argument vector.
- *
- * \return EXIT_SUCCESS or EXIT_FAILURE.
- */
-int main(int argc, char *argv[])
-{
-       int ret;
-       struct mixer *m;
-       struct mixer_handle *h = NULL;
-
-       fade_cmdline_parser(argc, argv, &conf);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       version_handle_flag("fade", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-       ret = configfile_exists();
-       if (!ret && conf.config_file_given) {
-               PARA_EMERG_LOG("can not read config file %s\n",
-                       conf.config_file_arg);
-               exit(EXIT_FAILURE);
-       }
-       if (ret) {
-               struct fade_cmdline_parser_params params = {
-                       .override = 0,
-                       .initialize = 0,
-                       .check_required = 0,
-                       .check_ambiguity = 0,
-                       .print_errors = 1
-               };
-               fade_cmdline_parser_config_file(conf.config_file_arg,
-                       &conf, &params);
-               loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       }
-       init_mixers();
-       m = get_mixer_or_die();
-       ret = m->open(conf.mixer_device_arg, &h);
-       if (ret < 0)
-               goto out;
-       ret = set_channel(m, h, conf.mixer_channel_arg);
-       if (ret == -E_BAD_CHANNEL) {
-               char *channels = m->get_channels(h);
-               printf("Available channels: %s\n", channels);
-               free(channels);
-       }
-       if (ret < 0)
-               goto out;
-       switch (conf.mode_arg) {
-       case mode_arg_fade:
-               ret = fade(m, h, conf.fade_vol_arg, conf.fade_time_arg);
-               break;
-       case mode_arg_snooze:
-               ret = snooze(m, h);
-               break;
-       case mode_arg_set:
-               ret = set_val(m, h);
-               break;
-       default: /* sleep mode */
-               ret = sweet_dreams(m, h);
-               break;
-       }
-out:
-       m->close(&h);
-       if (ret < 0)
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-       return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
-}
diff --git a/fd.c b/fd.c
index 6a26ce5e3d4d5f2993affc76a544e96db1a5738c..33891d2e6c9f1c3568428b1df93b4b92e295ef61 100644 (file)
--- a/fd.c
+++ b/fd.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file fd.c Helper functions for file descriptor handling. */
 
@@ -183,15 +179,15 @@ __printf_2_3 int write_va_buffer(int fd, const char *fmt, ...)
  * \param rfds An optional fd set pointer.
  * \param num_bytes Result pointer. Contains the number of bytes read from \a fd.
  *
- * If \a rfds is not \p NULL and the (non-blocking) file descriptor \a fd is
- * not set in \a rfds, this function returns early without doing anything.
- * Otherwise The function tries to read up to \a sz bytes from \a fd, where \a
- * sz is the sum of the lengths of all vectors in \a iov. As for xwrite(),
- * \p EAGAIN is not considered an error condition. However, \p EOF is.
+ * If rfds is not NULL and the (non-blocking) file descriptor fd is not set in
+ * rfds, this function returns early without doing anything. Otherwise it tries
+ * to read up to sz bytes from fd, where sz is the sum of the lengths of all
+ * vectors in iov. Like \ref xwrite(), EAGAIN and EINTR are not considered
+ * error conditions. However, EOF is.
  *
  * \return Zero or a negative error code. If the underlying call to readv(2)
  * returned zero (indicating an end of file condition) or failed for some
- * reason other than \p EAGAIN, a negative error code is returned.
+ * reason other than EAGAIN or EINTR, a negative error code is returned.
  *
  * In any case, \a num_bytes contains the number of bytes that have been
  * successfully read from \a fd (zero if the first readv() call failed with
@@ -230,7 +226,7 @@ int readv_nonblock(int fd, struct iovec *iov, int iovcnt, fd_set *rfds,
                if (ret == 0)
                        return -E_EOF;
                if (ret < 0) {
-                       if (errno == EAGAIN)
+                       if (errno == EAGAIN || errno == EINTR)
                                return 0;
                        return -ERRNO_TO_PARA_ERROR(errno);
                }
@@ -320,9 +316,9 @@ out:
  *
  * \param fn The file name.
  *
- * \return Non-zero iff file exists.
+ * \return True iff file exists.
  */
-int file_exists(const char *fn)
+bool file_exists(const char *fn)
 {
        struct stat statbuf;
 
@@ -405,7 +401,7 @@ __must_check int mark_fd_nonblocking(int fd)
  * This wrapper for FD_SET() passes its first two arguments to \p FD_SET. Upon
  * return, \a max_fileno contains the maximum of the old_value and \a fd.
  *
- * \sa para_select.
+ * \sa \ref para_select.
 */
 void para_fd_set(int fd, fd_set *fds, int *max_fileno)
 {
@@ -423,34 +419,6 @@ void para_fd_set(int fd, fd_set *fds, int *max_fileno)
        *max_fileno = PARA_MAX(*max_fileno, fd);
 }
 
-/**
- * Paraslash's wrapper for fgets(3).
- *
- * \param line Pointer to the buffer to store the line.
- * \param size The size of the buffer given by \a line.
- * \param f The stream to read from.
- *
- * \return Unlike the standard fgets() function, an integer value
- * is returned. On success, this function returns 1. On errors, -E_FGETS
- * is returned. A zero return value indicates an end of file condition.
- */
-__must_check int para_fgets(char *line, int size, FILE *f)
-{
-again:
-       if (fgets(line, size, f))
-               return 1;
-       if (feof(f))
-               return 0;
-       if (!ferror(f))
-               return -E_FGETS;
-       if (errno != EINTR) {
-               PARA_ERROR_LOG("%s\n", strerror(errno));
-               return -E_FGETS;
-       }
-       clearerr(f);
-       goto again;
-}
-
 /**
  * Paraslash's wrapper for mmap.
  *
@@ -459,22 +427,20 @@ again:
  * PROT_EXEC PROT_READ PROT_WRITE.
  * \param flags Exactly one of MAP_SHARED and MAP_PRIVATE.
  * \param fd The file to mmap from.
- * \param offset Mmap start.
  * \param map Result pointer.
  *
  * \return Standard.
  *
  * \sa mmap(2).
  */
-int para_mmap(size_t length, int prot, int flags, int fd, off_t offset,
-               void *map)
+int para_mmap(size_t length, int prot, int flags, int fd, void *map)
 {
        void **m = map;
 
        errno = EINVAL;
        if (!length)
                goto err;
-       *m = mmap(NULL, length, prot, flags, fd, offset);
+       *m = mmap(NULL, length, prot, flags, fd, (off_t)0);
        if (*m != MAP_FAILED)
                return 1;
 err:
@@ -573,20 +539,6 @@ close_cwd:
        return ret;
 }
 
-/**
- * A wrapper for fchdir().
- *
- * \param fd An open file descriptor.
- *
- * \return Standard.
- */
-static int para_fchdir(int fd)
-{
-       if (fchdir(fd) < 0)
-               return -ERRNO_TO_PARA_ERROR(errno);
-       return 1;
-}
-
 /**
  * A wrapper for mkdir(2).
  *
@@ -657,7 +609,7 @@ int mmap_full_file(const char *path, int open_mode, void **map,
        if (S_ISDIR(file_status.st_mode))
                goto out;
 
-       ret = para_mmap(*size, mmap_prot, mmap_flags, fd, 0, map);
+       ret = para_mmap(*size, mmap_prot, mmap_flags, fd, map);
 out:
        if (ret < 0 || !fd_ptr)
                close(fd);
@@ -674,7 +626,7 @@ out:
  *
  * \return Standard.
  *
- * \sa munmap(2), mmap_full_file().
+ * \sa munmap(2), \ref mmap_full_file().
  */
 int para_munmap(void *start, size_t length)
 {
@@ -716,8 +668,6 @@ int write_ok(int fd)
  *
  * Common approach that opens /dev/null until it gets a file descriptor greater
  * than two.
- *
- * \sa okir's Black Hats Manual.
  */
 void valid_fd_012(void)
 {
@@ -751,7 +701,7 @@ int for_each_file_in_dir(const char *dirname,
 {
        DIR *dir;
        struct dirent *entry;
-       int cwd_fd, ret2, ret = para_opendir(dirname, &dir, &cwd_fd);
+       int cwd_fd, ret = para_opendir(dirname, &dir, &cwd_fd);
 
        if (ret < 0)
                return ret == -ERRNO_TO_PARA_ERROR(EACCES)? 1 : ret;
@@ -787,9 +737,8 @@ int for_each_file_in_dir(const char *dirname,
        ret = 1;
 out:
        closedir(dir);
-       ret2 = para_fchdir(cwd_fd);
-       if (ret2 < 0 && ret >= 0)
-               ret = ret2;
+       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 29f387984c70455fec1f2ec2f27f844f88746c02..c9e79426f5c27ebf621367ce24b1c073c4e97e23 100644 (file)
--- a/fd.h
+++ b/fd.h
@@ -1,23 +1,17 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file fd.h exported symbols from fd.c */
 
 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, ...);
-int file_exists(const char *);
+bool file_exists(const char *);
 int para_select(int n, fd_set *readfds, fd_set *writefds,
                struct timeval *timeout_tv);
 __must_check int mark_fd_nonblocking(int fd);
 __must_check int mark_fd_blocking(int fd);
 void para_fd_set(int fd, fd_set *fds, int *max_fileno);
-__must_check int para_fgets(char *line, int size, FILE *f);
-int para_mmap(size_t length, int prot, int flags, int fd, off_t offset,
-               void *map);
+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);
index 1c3a37849d1309fbf3b676bf04c513628dc1c657..13d4f7b22f6a46da75ef4b629275d603fc5f39d1 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file fecdec_filter.c A filter that fec-decodes an audio stream. */
 
@@ -12,7 +8,6 @@
 #include "error.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
@@ -481,15 +476,9 @@ static void fecdec_open(struct filter_node *fn)
        fn->min_iqs = FEC_HEADER_SIZE;
 }
 
-/**
- * The init function of the fecdec filter.
- *
- * \param f Struct to initialize.
- */
-void fecdec_filter_init(struct filter *f)
-{
-       f->close = fecdec_close;
-       f->open = fecdec_open;
-       f->pre_select = generic_filter_pre_select;
-       f->post_select = fecdec_post_select;
-}
+const struct filter lsg_filter_cmd_com_fecdec_user_data = {
+       .open = fecdec_open,
+       .pre_select = generic_filter_pre_select,
+       .post_select = fecdec_post_select,
+       .close = fecdec_close,
+};
index 5e66607e4dba4a0595da7f8fcfeae0dba0bf3bad..9a5ed5d7fab7dc01fe46fa5ecab6940ea3a28828 100644 (file)
@@ -1,24 +1,19 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file file_write.c simple output plugin for testing purposes */
 
 #include <regex.h>
 #include <sys/types.h>
+#include <lopsub.h>
 
+#include "write_cmd.lsg.h"
 #include "para.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "write.h"
-#include "write_common.h"
 #include "string.h"
 #include "fd.h"
-#include "file_write.cmdline.h"
 #include "error.h"
 
 /** Data specific to the file writer. */
@@ -47,31 +42,31 @@ __must_check __malloc static char *random_filename(void)
 
 static int prepare_output_file(struct writer_node *wn)
 {
-       struct file_write_args_info *conf = wn->conf;
-       char *filename;
-       int ret;
-       struct private_file_write_data *pfwd = para_calloc(sizeof(*pfwd));
-
-       if (conf->filename_given)
-               filename = conf->filename_arg;
-       else
-               filename = random_filename();
-       ret = para_open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
-       if (!conf->filename_given)
-               free(filename);
-       if (ret < 0)
-               goto out;
-       pfwd->fd = ret;
-       ret = mark_fd_blocking(pfwd->fd);
+       const unsigned flags = O_WRONLY | O_CREAT, mode = S_IRUSR | S_IWUSR;
+       int ret, fd;
+       struct private_file_write_data *pfwd;
+
+
+       if (WRITE_CMD_OPT_GIVEN(FILE, FILENAME, wn->lpr)) {
+               const char *path = WRITE_CMD_OPT_STRING_VAL(FILE, FILENAME,
+                       wn->lpr);
+               ret = para_open(path, flags, mode);
+       } else {
+               char *path = random_filename();
+               ret = para_open(path, flags, mode);
+               free(path);
+       }
        if (ret < 0)
-               goto out_close;
-       wn->private_data = pfwd;
+               return ret;
+       fd = ret;
+       ret = mark_fd_blocking(fd);
+       if (ret < 0) {
+               close(fd);
+               return ret;
+       }
+       pfwd = wn->private_data = para_calloc(sizeof(*pfwd));
+       pfwd->fd = fd;
        return 1;
-out_close:
-       close(pfwd->fd);
-out:
-       free(pfwd);
-       return ret;
 }
 
 static void file_write_pre_select(struct sched *s, void *context)
@@ -131,31 +126,9 @@ out:
        return ret;
 }
 
-__malloc static void *file_write_parse_config_or_die(int argc, char **argv)
-{
-       struct file_write_args_info *conf = para_calloc(sizeof(*conf));
-
-       /* exits on errors */
-       file_write_cmdline_parser(argc, argv, conf);
-       return conf;
-}
-
-static void file_write_free_config(void *conf)
-{
-       file_write_cmdline_parser_free(conf);
-}
-
 /** the init function of the file writer */
-void file_write_init(struct writer *w)
-{
-       struct file_write_args_info dummy;
-
-       file_write_cmdline_parser_init(&dummy);
-       w->pre_select = file_write_pre_select;
-       w->post_select = file_write_post_select;
-       w->parse_config_or_die = file_write_parse_config_or_die;
-       w->free_config = file_write_free_config;
-       w->close = file_write_close;
-       w->help = (struct ggo_help)DEFINE_GGO_HELP(file_write);
-       file_write_cmdline_parser_free(&dummy);
-}
+struct writer lsg_write_cmd_com_file_user_data = {
+       .pre_select = file_write_pre_select,
+       .post_select = file_write_post_select,
+       .close = file_write_close,
+};
index 81901896aef25294a64599c9d77f63bf541f91e4..d4a2423904ca96f87f1ec11600f1c67e2b101ae1 100644 (file)
--- a/filter.c
+++ b/filter.c
@@ -1,31 +1,37 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file filter.c The stand-alone filter program. */
 
 #include <regex.h>
+#include <lopsub.h>
 
+#include "filter.lsg.h"
+#include "filter_cmd.lsg.h"
 #include "para.h"
-#include "filter.cmdline.h"
 #include "list.h"
+#include "lsu.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
 #include "stdin.h"
 #include "stdout.h"
 #include "error.h"
+#include "fd.h"
 #include "version.h"
 
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
+static struct lls_parse_result *lpr; /* command line options */
+
+#define CMD_PTR (lls_cmd(0, filter_suite))
+#define OPT_RESULT(_name) \
+       (lls_opt_result(LSG_FILTER_PARA_FILTER_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
 
 /** The list of all status items used by para_{server,audiod,gui}. */
-const char *status_item_list[] = {STATUS_ITEM_ARRAY};
+const char *status_item_list[] = {STATUS_ITEMS};
 
 /**
  * Dummy version which only contains NULL pointers.
@@ -45,48 +51,40 @@ static struct stdout_task stdout_task_struct;
 /** Pointer to the stdout task. */
 static struct stdout_task *sot = &stdout_task_struct;
 
-/** Gengetopt struct that holds the command line args. */
-static struct filter_args_info conf;
-
 static int loglevel;
 INIT_STDERR_LOGGING(loglevel);
 
-__noreturn static void print_help_and_die(void)
+static void handle_help_flag(void)
 {
-       struct ggo_help h = DEFINE_GGO_HELP(filter);
-       bool d = conf.detailed_help_given;
-
-       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
-       print_filter_helps(d? GPH_MODULE_FLAGS_DETAILED : GPH_MODULE_FLAGS);
+       char *help;
+
+       if (OPT_GIVEN(DETAILED_HELP))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       print_filter_helps(OPT_GIVEN(DETAILED_HELP));
        exit(EXIT_SUCCESS);
 }
 
 static int parse_config(void)
 {
-       static char *cf; /* config file */
-       struct stat statbuf;
-
-       version_handle_flag("filter", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-       if (!cf) {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/filter.conf", home);
-               free(home);
-       }
-       if (!stat(cf, &statbuf)) {
-               struct filter_cmdline_parser_params params = {
-                       .override = 0,
-                       .initialize = 0,
-                       .check_required = 0,
-                       .check_ambiguity = 0,
-                       .print_errors = 1
-               };
-               filter_cmdline_parser_config_file(cf, &conf, &params);
-               loglevel = get_loglevel_by_name(conf.loglevel_arg);
+       int ret;
+
+       version_handle_flag("filter", OPT_GIVEN(VERSION));
+       handle_help_flag();
+       ret = lsu_merge_config_file_options(NULL, "filter.conf",
+               &lpr, CMD_PTR, filter_suite, 0 /* default flags */);
+       if (ret < 0)
+               return ret;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       if (!OPT_GIVEN(FILTER)) {
+               print_filter_list();
+               exit(EXIT_SUCCESS);
        }
-       if (!conf.filter_given)
-               return -E_NO_FILTERS;
        return 1;
 }
 
@@ -109,36 +107,36 @@ int main(int argc, char *argv[])
        const struct filter *f;
        struct btr_node *parent;
        struct filter_node **fns;
+       struct lls_parse_result *filter_lpr; /* per-filter options */
+       char *errctx;
 
-       filter_cmdline_parser(argc, argv, &conf); /* aborts on errors */
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       filter_init();
-       ret = parse_config();
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
        if (ret < 0)
                goto out;
+       ret = parse_config();
+       if (ret < 0)
+               goto free_lpr;
        sit->btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "stdin"));
        stdin_task_register(sit, &s);
 
-       fns = para_malloc(conf.filter_given * sizeof(*fns));
-       for (i = 0, parent = sit->btrn; i < conf.filter_given; i++) {
-               char *fa = conf.filter_arg[i];
+       fns = para_malloc(OPT_GIVEN(FILTER) * sizeof(*fns));
+       for (i = 0, parent = sit->btrn; i < OPT_GIVEN(FILTER); i++) {
+               const char *fa = lls_string_val(i, OPT_RESULT(FILTER));
+               const char *name;
                struct filter_node *fn;
                struct task_info ti;
 
                fn = fns[i] = para_calloc(sizeof(*fn));
-               ret = check_filter_arg(fa, &fn->conf);
-               if (ret < 0) {
-                       free(fn);
-                       goto out_cleanup;
-               }
-               fn->filter_num = ret;
+               fn->filter_num = filter_setup(fa, &fn->conf, &filter_lpr);
+               name = filter_name(fn->filter_num);
+               fn->lpr = filter_lpr;
+               PARA_DEBUG_LOG("filter #%d: %s\n", i, name);
                f = filter_get(fn->filter_num);
-               PARA_DEBUG_LOG("filter #%d: %s\n", i, f->name);
                fn->btrn = btr_new_node(&(struct btr_node_description)
-                       EMBRACE(.name = f->name, .parent = parent,
+                       EMBRACE(.name = name, .parent = parent,
                        .handler = f->execute, .context = fn));
-               ti.name = f->name;
+               ti.name = name;
                ti.pre_select = f->pre_select;
                ti.post_select = f->post_select;
                ti.context = fn;
@@ -156,7 +154,6 @@ int main(int argc, char *argv[])
        btr_log_tree(sit->btrn, LL_INFO);
        ret = schedule(&s);
        sched_shutdown(&s);
-out_cleanup:
        for (i--; i >= 0; i--) {
                struct filter_node *fn = fns[i];
 
@@ -164,15 +161,21 @@ out_cleanup:
                if (f->close)
                        f->close(fn);
                btr_remove_node(&fn->btrn);
-               if (f->free_config)
-                       f->free_config(fn->conf);
+               if (f->teardown)
+                       f->teardown(fn->lpr, fn->conf);
                free(fn);
        }
        free(fns);
        btr_remove_node(&sit->btrn);
        btr_remove_node(&sot->btrn);
+free_lpr:
+       lls_free_parse_result(lpr, CMD_PTR);
 out:
-       if (ret < 0)
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       }
        exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
 }
index 0bd546903927d412fccef946cf8f43783ed8bc70..69d4dfee8534941ab74bb17d7d2a5ff8e23eed6b 100644 (file)
--- a/filter.h
+++ b/filter.h
@@ -1,14 +1,7 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file filter.h Filter-related structures and exported symbols from filter_common.c. */
 
-/** The list of supported filters. */
-enum filter_enum {FILTER_ENUM};
-
 /**
  * Describes one running instance of a filter.
 */
@@ -24,6 +17,8 @@ struct filter_node {
        struct list_head callbacks;
        /** A pointer to the configuration of this instance. */
        void *conf;
+       /** The parsed command line, merged with options given in the config file. */
+       struct lls_parse_result *lpr;
        /** The buffer tree node. */
        struct btr_node *btrn;
        /** The task corresponding to this filter node. */
@@ -35,26 +30,16 @@ struct filter_node {
 /**
  * The structure associated with a paraslash filter.
  *
- * Paraslash filters are "modules" which are used to transform an audio stream.
- * struct filter contains pointers to functions that must be supplied by the
- * filter code in order to be used by the driving application (currently
- * para_audiod and para_filter).
+ * Paraslash filters are "modules" which transform an audio stream. struct
+ * filter contains methods which are implemented by each filter.
  *
  * Note: As several instances of the same filter may be running at the same
  * time, all these filter functions must be reentrant; no static non-constant
  * variables may be used.
- * \sa mp3dec_filter.c, oggdec_filter.c, wav_filter.c, compress_filter.c, filter_node
+ *
+ * \sa \ref filter_node.
  */
 struct filter {
-       /** The name of the filter. */
-       const char *name;
-       /**
-        * Pointer to the filter init routine.
-        *
-        * This function is only called once at startup. It must initialize the
-        * other non-optional function pointers of this structure.
-        */
-       void (*init)(struct filter *f);
        /**
         * Open one instance of this filter.
         *
@@ -73,27 +58,29 @@ struct filter {
         */
        void (*close)(struct filter_node *fn);
        /**
-        * A pointer to the filter's command line parser.
+        * Prepare the filter according to command line options.
+        *
+        * In addition to the syntactic checks which are automatically performed
+        * by the lopsub functions, some filters like to also check the command
+        * line arguments semantically. Moreover, since applications may open
+        * the filter many times with the same options, filters need a method
+        * which allows them to precompute once those parts of the setup which
+        * depend only on the command line options.
+        *
+        * If this function pointer is not NULL, the function is called once at
+        * startup. The returned pointer value is made available to the ->open
+        * method via the ->conf pointer of struct filter_node.
         *
-        * If this optional function pointer is not NULL, any filter options
-        * are passed from the main program to this command line parser once at
-        * application startup. The command line parser should check its
-        * command line options given by \a argc and \a argv and abort on
-        * errors. Success must be indicated by a non-negative return value. In
-        * this case the function should return a pointer to the
-        * filter-specific configuration data determined by \a argc and \a
-        * argv. On failure, a negative paraslash error code must be returned.
+        * Filters are supposed to abort if the setup fails. If the function
+        * returns, it is assumed to have succeeded.
         */
-       int (*parse_config)(int argc, char **argv, void **config);
+       void *(*setup)(const struct lls_parse_result *lpr);
        /**
-        * Deallocate the memory for the configuration.
+        * Deallocate precomputed resources.
         *
-        * This is called to free whatever ->parse_config() has allocated.
+        * This should free whatever ->setup() has allocated.
         */
-       void (*free_config)(void *conf);
-
-       /** The help texts for this filter. */
-       struct ggo_help help;
+       void (*teardown)(const struct lls_parse_result *lpr, void *conf);
        /**
         * Set scheduler timeout and add file descriptors to fd sets.
         *
@@ -121,9 +108,22 @@ struct filter {
        btr_command_handler execute;
 };
 
-void filter_init(void);
-int check_filter_arg(const char *fa, void **conf);
-void print_filter_helps(unsigned flags);
+void print_filter_helps(bool detailed);
+void print_filter_list(void);
+int filter_setup(const char *fa, void **conf, struct lls_parse_result **lprp);
+#define FILTER_CMD(_num) (lls_cmd(_num, filter_cmd_suite))
+#define FILTER_CMD_OPT(_cmd, _opt) (lls_opt( \
+       LSG_FILTER_CMD_ ## _cmd ## _OPT_ ## _opt, \
+       FILTER_CMD(LSG_FILTER_CMD_CMD_ ## _cmd)))
+#define FILTER_CMD_OPT_RESULT(_cmd, _opt, _lpr) \
+       (lls_opt_result(LSG_FILTER_CMD_ ## _cmd ## _OPT_ ## _opt, _lpr))
+#define FILTER_CMD_OPT_GIVEN(_cmd, _opt, _lpr) \
+       (lls_opt_given(FILTER_CMD_OPT_RESULT(_cmd, _opt, _lpr)))
+#define FILTER_CMD_OPT_UINT32_VAL(_cmd, _opt, _lpr) \
+       (lls_uint32_val(0, FILTER_CMD_OPT_RESULT(_cmd, _opt, _lpr)))
+#define FILTER_CMD_OPT_STRING_VAL(_cmd, _opt, _lpr) \
+       (lls_string_val(0, FILTER_CMD_OPT_RESULT(_cmd, _opt, _lpr)))
+
 void generic_filter_pre_select(struct sched *s, void *context);
 int decoder_execute(const char *cmd, unsigned sample_rate, unsigned channels,
                char **result);
@@ -139,6 +139,5 @@ static inline void write_int16_host_endian(char *buf, int val)
 #endif
 }
 
-DECLARE_FILTER_INITS
-
 const struct filter *filter_get(int filter_num);
+const char *filter_name(int filter_num);
index e9b97e54633a8770009c0f746b7daa712d4135fb..add788a8f30465251ff111822b7131cd2e94054b 100644 (file)
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file filter_common.c Common helper functions for filter input/output. */
 
 #include <regex.h>
 #include <sys/types.h>
+#include <lopsub.h>
 
+#include "filter_cmd.lsg.h"
 #include "para.h"
 #include "list.h"
 #include "sched.h"
 #include "fd.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "error.h"
 #include "string.h"
 
-/** Iterate over the array of supported filters. */
-#define FOR_EACH_SUPPORTED_FILTER(j)  for (j = 0; j < NUM_SUPPORTED_FILTERS; j++)
-
-/** The array of supported filters. */
-static struct filter filters[NUM_SUPPORTED_FILTERS] = {FILTER_ARRAY};
+/** Iterate over all filters. */
+#define FOR_EACH_FILTER(j) for (j = 1; FILTER_CMD(j); j++)
 
 /**
  * Obtain a reference to a filter structure.
  *
  * \param filter_num Between zero and NUM_SUPPORTED_FILTERS, inclusively.
  *
- * \return Pointer to the filter identified by the given filter number.
+ * \return Pointer to the filter identified by the given filter number, or
+ * NULL if the filter number is out of range.
  *
- * It is a fatal error if the given number is out of range. In this case
- * the function aborts.
+ * \sa filter_name().
  */
 const struct filter *filter_get(int filter_num)
 {
-       assert(filter_num >= 0);
-       assert(filter_num < NUM_SUPPORTED_FILTERS);
-       return filters + filter_num;
+       if (filter_num < 1 || filter_num > LSG_NUM_FILTER_CMD_SUBCOMMANDS)
+               return NULL;
+       return lls_user_data(FILTER_CMD(filter_num));
 }
 
-/**
- * Call the init function of each supported filter.
- * \sa filter::init
- */
-void filter_init(void)
+static inline bool filter_supported(int filter_num)
 {
-       int i;
-
-       FOR_EACH_SUPPORTED_FILTER(i)
-               filter_get(i)->init((struct filter *)filter_get(i));
+       return lls_user_data(FILTER_CMD(filter_num));
 }
 
-/*
- * If the filter has a command line parser and options is not NULL, run it.
- * Returns filter_num on success, negative on errors
+/**
+ * Return the name of a filter, given its number.
+ *
+ * \param filter_num See \ref filter_get().
+ *
+ * \return A pointer to a string literal, or NULL if filter_num is out of
+ * range. The caller must not attempt to call free(3) on the returned pointer.
  */
-static int parse_filter_args(int filter_num, const char *options, void **conf)
+const char *filter_name(int filter_num)
 {
-       const struct filter *f = filter_get(filter_num);
-       int ret, argc;
-       char **argv;
-
-       if (!f->parse_config)
-               return strlen(options)? -E_BAD_FILTER_OPTIONS : filter_num;
-       argc = create_shifted_argv(options, " \t", &argv);
-       if (argc < 0)
-               return -E_BAD_FILTER_OPTIONS;
-       argv[0] = para_strdup(f->name);
-       ret = f->parse_config(argc, argv, conf);
-       free_argv(argv);
-       return ret < 0? ret : filter_num;
+       if (filter_num < 1 || filter_num > LSG_NUM_FILTER_CMD_SUBCOMMANDS)
+               return NULL;
+       return lls_command_name(FILTER_CMD(filter_num));
 }
 
 /**
- * Check the filter command line options.
+ * Parse a filter command line and call the corresponding ->setup method.
  *
  * \param fa The filter argument.
- * \param conf Points to the filter configuration upon successful return.
+ * \param conf Points to filter-specific setup upon successful return.
+ * \param lprp Parsed command line options are returned here.
  *
  * Check if the given filter argument starts with the name of a supported
  * filter, optionally followed by options for this filter. If yes, call the
- * command line parser of that filter.
- *
- * \return On success, the number of the filter is returned and \a conf
- * is initialized to point to the filter configuration determined by \a fa.
- * On errors, a negative value is returned.
+ * command line parser of that filter and its ->setup method.
  *
- * Note: If \a fa specifies a filter that has no command line parser success is
- * returned, and \a conf is initialized to \p NULL.
- *
- * \sa filter::parse_config
+ * \return This function either succeeds or does not return. On success, the
+ * number of the filter is returned and conf is initialized to point to the
+ * filter configuration as returned by the filter's ->setup() method, if any.
+ * Moreover, *lprp is initialized to contain the parsed command line options.
+ * On errors, the function calls exit(EXIT_FAILURE).
  */
-int check_filter_arg(const char *fa, void **conf)
+int filter_setup(const char *fa, void **conf, struct lls_parse_result **lprp)
 {
-       int j;
-
-       *conf = NULL;
-//     PARA_DEBUG_LOG("arg: %s\n", fa);
-       FOR_EACH_SUPPORTED_FILTER(j) {
-               const char *name = filter_get(j)->name;
-               size_t len = strlen(name);
-               char c;
-               if (strlen(fa) < len)
-                       continue;
-               if (strncmp(name, fa, len))
-                       continue;
-               c = fa[len];
-               if (c && c != ' ')
-                       continue;
-               if (c && !filter_get(j)->parse_config)
-                       return -E_BAD_FILTER_OPTIONS;
-               return parse_filter_args(j, c? fa + len + 1 :
-                       fa + strlen(fa), conf);
+       int ret, filter_num, argc;
+       char *errctx = NULL, **argv;
+       const struct lls_command *cmd;
+       const struct filter *f;
+
+       ret = create_argv(fa, " \t\n", &argv);
+       if (ret < 0)
+               goto fail;
+       argc = ret;
+       ret = lls(lls_lookup_subcmd(argv[0], filter_cmd_suite, &errctx));
+       if (ret < 0)
+               goto free_argv;
+       filter_num = ret;
+       cmd = FILTER_CMD(filter_num);
+       if (!filter_supported(filter_num)) {
+               ret = -E_UNSUPPORTED_FILTER;
+               errctx = make_message("bad filter name: %s",
+                       lls_command_name(cmd));
+               goto free_argv;
        }
-       return -E_UNSUPPORTED_FILTER;
+       ret = lls(lls_parse(argc, argv, cmd, lprp, &errctx));
+       if (ret < 0)
+               goto free_argv;
+       f = filter_get(filter_num);
+       *conf = f->setup? f->setup(*lprp) : NULL;
+       ret = filter_num;
+free_argv:
+       free_argv(argv);
+       if (ret >= 0)
+               return ret;
+fail:
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", errctx);
+       free(errctx);
+       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       exit(EXIT_FAILURE);
 }
 
 /**
  * Print help text of each filter to stdout.
  *
- * \param flags Passed to \ref ggo_print_help().
+ * \param detailed Whether to print short or long help.
  */
-void print_filter_helps(unsigned flags)
+void print_filter_helps(bool detailed)
 {
        int i, num = 0;
 
-       printf_or_die("\nAvailable filters: ");
-       FOR_EACH_SUPPORTED_FILTER(i) {
+       printf("\nAvailable filters: ");
+       FOR_EACH_FILTER(i) {
+               if (!filter_supported(i))
+                       continue;
                if (num > 50) {
-                       printf_or_die("\n                  ");
+                       printf("\n                  ");
                        num = 0;
                }
-               num += printf_or_die("%s%s", i? " " : "", filter_get(i)->name);
+               num += printf("%s%s", i? " " : "", filter_name(i));
+       }
+       printf("\n");
+
+       FOR_EACH_FILTER(i) {
+               const struct lls_command *cmd = FILTER_CMD(i);
+               char *help;
+
+               if (!filter_supported(i))
+                       continue;
+               help = detailed? lls_long_help(cmd) : lls_short_help(cmd);
+               if (!help)
+                       continue;
+               printf("%s\n", help);
+               free(help);
        }
-       printf_or_die("\n");
+}
 
-       FOR_EACH_SUPPORTED_FILTER(i) {
-               struct filter *f = (struct filter *)filter_get(i);
+/**
+ * Print a short summary of all available filters to stdout.
+ *
+ * For each supported filter, the filter name and the purpose text is printed
+ * in a single line. Since no options are shown, the filter list is more
+ * concise than the text obtained from print_filter_helps().
+ */
+void print_filter_list(void)
+{
+       int i;
 
-               if (!f->help.short_help)
+       printf("Available filters:\n");
+       FOR_EACH_FILTER(i) {
+               const struct lls_command *cmd = FILTER_CMD(i);
+               if (!filter_supported(i))
                        continue;
-               printf_or_die("\nOptions for %s (%s):", f->name,
-                       f->help.purpose);
-               ggo_print_help(&f->help, flags);
+               printf("%-9s %s\n", filter_name(i), lls_purpose(cmd));
        }
 }
 
index 385d4f0c3a0235b9be24cdc83666308c30b959b8..6e23683937f6932aaa739095ef8281fc9075da55 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file flac_afh.c Audio format handler for flac files. */
 
@@ -391,6 +387,7 @@ static int flac_afh_read_chunks(struct private_flac_afh_data *pfad)
                        break;
        }
        afhi->chunks_total = c;
+       set_max_chunk_size(afhi);
        ret = 1;
 free_decoder:
        FLAC__stream_decoder_finish(decoder);
@@ -518,13 +515,12 @@ free_pfad:
 static const char * const flac_suffixes[] = {"flac", NULL};
 
 /**
- * The init function of the flac audio format handler.
+ * The audio format handler for flac (free lossless audio decoder).
  *
- * \param afh pointer to the struct to initialize
+ * It depends on libflac and on libogg.
  */
-void flac_afh_init(struct audio_format_handler *afh)
-{
-       afh->get_file_info = flac_get_file_info,
-       afh->suffixes = flac_suffixes;
-       afh->rewrite_tags = flac_rewrite_tags;
-}
+const struct audio_format_handler flac_afh = {
+       .get_file_info = flac_get_file_info,
+       .suffixes = flac_suffixes,
+       .rewrite_tags = flac_rewrite_tags,
+};
index bbacb3dad87025073b5bf8e99dc1ea8fa6bf5800..6a3a8effaf83b3b9e333e429762f0ff2d3938afe 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file flacdec_filter.c The flac decoder. */
 
@@ -12,7 +8,6 @@
 #include "para.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "error.h"
@@ -296,18 +291,10 @@ static void flacdec_open(struct filter_node *fn)
        fn->min_iqs = 0;
 }
 
-/**
- * The init function of the flacdec filter.
- *
- * \param f Pointer to the filter struct to initialize.
- *
- * \sa filter::init.
- */
-void flacdec_filter_init(struct filter *f)
-{
-       f->open = flacdec_open;
-       f->close = flacdec_close;
-       f->pre_select = flacdec_pre_select;
-       f->post_select = flacdec_post_select;
-       f->execute = flacdec_execute;
-}
+const struct filter lsg_filter_cmd_com_flacdec_user_data = {
+       .open = flacdec_open,
+       .close = flacdec_close,
+       .pre_select = flacdec_pre_select,
+       .post_select = flacdec_post_select,
+       .execute = flacdec_execute,
+};
index f30e8166b01badc08de69487dcb99645d4d15990..dbe4900862fef83a552d9c60d9ac21d4c8253d5e 100644 (file)
--- a/gcrypt.c
+++ b/gcrypt.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file gcrypt.c Libgrcypt-based encryption/decryption routines. */
 
@@ -19,9 +15,6 @@
 
 //#define GCRYPT_DEBUG 1
 
-static bool libgcrypt_has_oaep;
-static const char *rsa_decrypt_sexp;
-
 #ifdef GCRYPT_DEBUG
 static void dump_buffer(const char *msg, unsigned char *buf, int len)
 {
@@ -61,137 +54,47 @@ void get_random_bytes_or_die(unsigned char *buf, int num)
 /*
  * This is called at the beginning of every program that uses libgcrypt. The
  * call to gcry_check_version() initializes the gcrypt library and checks that
- * we have at least the minimal required version. This function also tells us
- * whether we have to use our own OAEP padding code.
+ * we have at least the minimal required version.
  */
-void init_random_seed_or_die(void)
+void crypt_init(void)
 {
-       const char *ver, *req_ver;
+       const char *req_ver = "1.5.0";
        int seed;
 
-       ver = gcry_check_version(NULL);
-       req_ver = "1.4.0";
        if (!gcry_check_version(req_ver)) {
                PARA_EMERG_LOG("fatal: need at least libgcrypt-%s, have: %s\n",
-                       req_ver, ver);
+                       req_ver, gcry_check_version(NULL));
                exit(EXIT_FAILURE);
        }
-       req_ver = "1.5.0";
-       if (gcry_check_version(req_ver)) {
-               libgcrypt_has_oaep = true;
-               rsa_decrypt_sexp = "(enc-val(flags oaep)(rsa(a %m)))";
-       } else {
-               libgcrypt_has_oaep = false;
-               rsa_decrypt_sexp = "(enc-val(rsa(a %m)))";
-       }
-       get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed));
-       srandom(seed);
-}
 
-/** S-expression for the public part of an RSA key. */
-#define RSA_PUBKEY_SEXP "(public-key (rsa (n %m) (e %m)))"
-/** S-expression for a private RSA key. */
-#define RSA_PRIVKEY_SEXP "(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))"
+       /*
+        * Allocate a pool of secure memory. This also drops privileges where
+        * needed.
+        */
+       gcry_control(GCRYCTL_INIT_SECMEM, 65536, 0);
 
-/* rfc 3447, appendix B.2 */
-static void mgf1(unsigned char *seed, size_t seed_len, unsigned result_len,
-               unsigned char *result)
-{
-       gcry_error_t gret;
-       gcry_md_hd_t handle;
-       size_t n;
-       unsigned char *md;
-       unsigned char octet_string[4], *rp = result, *end = rp + result_len;
+       /* Tell Libgcrypt that initialization has completed. */
+       gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
 
-       assert(result_len / HASH_SIZE < 1ULL << 31);
-       gret = gcry_md_open(&handle, GCRY_MD_SHA1, 0);
-       assert(gret == 0);
-       for (n = 0; rp < end; n++) {
-               gcry_md_write(handle, seed, seed_len);
-               octet_string[0] = (unsigned char)((n >> 24) & 255);
-               octet_string[1] = (unsigned char)((n >> 16) & 255);
-               octet_string[2] = (unsigned char)((n >> 8)) & 255;
-               octet_string[3] = (unsigned char)(n & 255);
-               gcry_md_write(handle, octet_string, 4);
-               gcry_md_final(handle);
-               md = gcry_md_read(handle, GCRY_MD_SHA1);
-               memcpy(rp, md, PARA_MIN(HASH_SIZE, (int)(end - rp)));
-               rp += HASH_SIZE;
-               gcry_md_reset(handle);
-       }
-       gcry_md_close(handle);
+       get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed));
+       srandom(seed);
 }
 
-/** The sha1 hash of an empty file. */
-static const unsigned char empty_hash[HASH_SIZE] =
-       "\xda" "\x39" "\xa3" "\xee" "\x5e"
-       "\x6b" "\x4b" "\x0d" "\x32" "\x55"
-       "\xbf" "\xef" "\x95" "\x60" "\x18"
-       "\x90" "\xaf" "\xd8" "\x07" "\x09";
-
-/* rfc3447, section 7.1.1 */
-static void pad_oaep(unsigned char *in, size_t in_len, unsigned char *out,
-               size_t out_len)
+void crypt_shutdown(void)
 {
-       size_t ps_len = out_len - in_len - 2 * HASH_SIZE - 2;
-       size_t n, mask_len = out_len - HASH_SIZE - 1;
-       unsigned char *seed = out + 1, *db = seed + HASH_SIZE,
-               *ps = db + HASH_SIZE, *one = ps + ps_len;
-       unsigned char *db_mask, seed_mask[HASH_SIZE];
-
-       assert(in_len <= out_len - 2 - 2 * HASH_SIZE);
-       assert(out_len > 2 * HASH_SIZE + 2);
-       PARA_DEBUG_LOG("padding %zu byte input -> %zu byte output\n",
-               in_len, out_len);
-       dump_buffer("unpadded buffer", in, in_len);
-
-       out[0] = '\0';
-       get_random_bytes_or_die(seed, HASH_SIZE);
-       memcpy(db, empty_hash, HASH_SIZE);
-       memset(ps, 0, ps_len);
-       *one = 0x01;
-       memcpy(one + 1, in, in_len);
-       db_mask = para_malloc(mask_len);
-       mgf1(seed, HASH_SIZE, mask_len, db_mask);
-       for (n = 0; n < mask_len; n++)
-               db[n] ^= db_mask[n];
-       mgf1(db, mask_len, HASH_SIZE, seed_mask);
-       for (n = 0; n < HASH_SIZE; n++)
-               seed[n] ^= seed_mask[n];
-       free(db_mask);
-       dump_buffer("padded buffer", out, out_len);
+       /*
+        * WK does not see a way to apply a patch for the sake of Valgrind, so
+        * as of 2018 libgrypt has no deinitialization routine to free the
+        * resources on exit.
+        */
 }
 
-/* rfc 3447, section 7.1.2 */
-static int unpad_oaep(unsigned char *in, size_t in_len, unsigned char *out,
-               size_t *out_len)
-{
-       unsigned char *masked_seed = in + 1;
-       unsigned char *db = in + 1 + HASH_SIZE;
-       unsigned char seed[HASH_SIZE], seed_mask[HASH_SIZE];
-       unsigned char *db_mask, *p;
-       size_t n, mask_len = in_len - HASH_SIZE - 1;
-
-       mgf1(db, mask_len, HASH_SIZE, seed_mask);
-       for (n = 0; n < HASH_SIZE; n++)
-               seed[n] = masked_seed[n] ^ seed_mask[n];
-       db_mask = para_malloc(mask_len);
-       mgf1(seed, HASH_SIZE, mask_len, db_mask);
-       for (n = 0; n < mask_len; n++)
-               db[n] ^= db_mask[n];
-       free(db_mask);
-       if (memcmp(db, empty_hash, HASH_SIZE))
-               return -E_OEAP;
-       for (p = db + HASH_SIZE; p < in + in_len - 1; p++)
-               if (*p != '\0')
-                       break;
-       if (p >= in + in_len - 1)
-               return -E_OEAP;
-       p++;
-       *out_len = in + in_len - p;
-       memcpy(out, p, *out_len);
-       return 1;
-}
+/** S-expression for the public part of an RSA key. */
+#define RSA_PUBKEY_SEXP "(public-key (rsa (n %m) (e %m)))"
+/** S-expression for a private RSA key. */
+#define RSA_PRIVKEY_SEXP "(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))"
+/** S-expression for decryption. */
+#define RSA_DECRYPT_SEXP "(enc-val(flags oaep)(rsa(a %m)))"
 
 struct asymmetric_key {
        gcry_sexp_t sexp;
@@ -203,66 +106,6 @@ static const char *gcrypt_strerror(gcry_error_t gret)
        return gcry_strerror(gcry_err_code(gret));
 }
 
-static int decode_key(const char *key_file, const char *header_str,
-               const char *footer_str, unsigned char **result)
-{
-       int ret, ret2, i, j;
-       void *map;
-       size_t map_size, key_size, blob_size;
-       unsigned char *blob = NULL;
-       char *begin, *footer, *key;
-
-       ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
-       if (ret < 0)
-               goto out;
-       ret = -E_KEY_MARKER;
-       if (strncmp(map, header_str, strlen(header_str)))
-               goto unmap;
-       footer = strstr(map, footer_str);
-       ret = -E_KEY_MARKER;
-       if (!footer)
-               goto unmap;
-       begin = map + strlen(header_str);
-       /* skip whitespace at the beginning */
-       for (; begin < footer; begin++) {
-               if (para_isspace(*begin))
-                       continue;
-               break;
-       }
-       ret = -E_KEY_MARKER;
-       if (begin >= footer)
-               goto unmap;
-
-       key_size = footer - begin;
-       key = para_malloc(key_size + 1);
-       for (i = 0, j = 0; begin + i < footer; i++) {
-               if (para_isspace(begin[i]))
-                       continue;
-               key[j++] = begin[i];
-       }
-       key[j] = '\0';
-       ret = base64_decode(key, j, (char **)&blob, &blob_size);
-       free(key);
-       if (ret < 0)
-               goto free_unmap;
-       ret = blob_size;
-       goto unmap;
-free_unmap:
-       free(blob);
-       blob = NULL;
-unmap:
-       ret2 = para_munmap(map, map_size);
-       if (ret >= 0 && ret2 < 0)
-               ret = ret2;
-       if (ret < 0) {
-               free(blob);
-               blob = NULL;
-       }
-out:
-       *result = blob;
-       return ret;
-}
-
 /** ASN Types and their code. */
 enum asn1_types {
        /** The next object is an integer. */
@@ -302,71 +145,13 @@ static inline int get_long_form_num_length_bytes(unsigned char c)
        return c & 0x7f;
 }
 
-static int find_pubkey_bignum_offset(const unsigned char *data, int len)
-{
-       const unsigned char *p = data, *end = data + len;
-
-       /* the whole thing starts with one sequence */
-       if (*p != ASN1_TYPE_SEQUENCE)
-               return -E_ASN1_PARSE;
-       p++;
-       if (p >= end)
-               return -E_ASN1_PARSE;
-       if (is_short_form(*p))
-               p++;
-       else
-               p += 1 + get_long_form_num_length_bytes(*p);
-       if (p >= end)
-               return -E_ASN1_PARSE;
-       /* another sequence containing the object id, skip it */
-       if (*p != ASN1_TYPE_SEQUENCE)
-               return -E_ASN1_PARSE;
-       p++;
-       if (p >= end)
-               return -E_ASN1_PARSE;
-       if (!is_short_form(*p))
-               return -E_ASN1_PARSE;
-       p += 1 + get_short_form_length(*p);
-       if (p >= end)
-               return -E_ASN1_PARSE;
-       /* all numbers are wrapped in a bit string object that follows */
-       if (*p != ASN1_TYPE_BIT_STRING)
-               return -E_ASN1_PARSE;
-       p++;
-       if (p >= end)
-               return -E_ASN1_PARSE;
-       if (is_short_form(*p))
-               p++;
-       else
-               p += 1 + get_long_form_num_length_bytes(*p);
-       p++; /* skip number of unused bits in the bit string */
-       if (p >= end)
-               return -E_ASN1_PARSE;
-
-       /* next, we have a sequence of two integers (n and e) */
-       if (*p != ASN1_TYPE_SEQUENCE)
-               return -E_ASN1_PARSE;
-       p++;
-       if (p >= end)
-               return -E_ASN1_PARSE;
-       if (is_short_form(*p))
-               p++;
-       else
-               p += 1 + get_long_form_num_length_bytes(*p);
-       if (p >= end)
-               return -E_ASN1_PARSE;
-       if (*p != ASN1_TYPE_INTEGER)
-               return -E_ASN1_PARSE;
-       return p - data;
-}
-
 /*
  * Returns: Number of bytes scanned. This may differ from the value returned via
- * bn_bytes because the latter does not include the ASN.1 prefix and a leading
- * zero is not considered as an additional byte for bn_bytes.
+ * bitsp because the latter does not include the ASN.1 prefix and a leading
+ * zero is not considered as an additional byte for the number of bits.
  */
-static int read_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn,
-               int *bn_bytes)
+static int read_pem_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn,
+               unsigned *bitsp)
 {
        int i, bn_size;
        gcry_error_t gret;
@@ -392,7 +177,7 @@ static int read_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn,
        PARA_DEBUG_LOG("bn_size %d (0x%x)\n", bn_size, (unsigned)bn_size);
        gret = gcry_mpi_scan(bn, GCRYMPI_FMT_STD, cp, bn_size, NULL);
        if (gret) {
-               PARA_ERROR_LOG("%s while scanning n\n",
+               PARA_ERROR_LOG("gcry_mpi_scan: %s\n",
                        gcry_strerror(gcry_err_code(gret)));
                return-E_MPI_SCAN;
        }
@@ -404,8 +189,8 @@ static int read_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn,
                cp++;
                bn_size--;
        }
-       if (bn_bytes)
-               *bn_bytes = bn_size;
+       if (bitsp)
+               *bitsp = bn_size * 8;
        cp += bn_size;
 //     unsigned char *buf;
 //     gcry_mpi_aprint(GCRYMPI_FMT_HEX, &buf, NULL, *bn);
@@ -413,7 +198,55 @@ static int read_bignum(unsigned char *start, unsigned char *end, gcry_mpi_t *bn,
        return cp - start;
 }
 
-static int find_privkey_bignum_offset(const unsigned char *data, int len)
+struct rsa_params {
+       gcry_mpi_t n, e, d, p, q, u;
+};
+
+static int read_pem_rsa_params(unsigned char *start, unsigned char *end,
+               struct rsa_params *p)
+{
+       unsigned char *cp = start;
+       unsigned bits;
+       int ret;
+
+       ret = read_pem_bignum(cp, end, &p->n, &bits);
+       if (ret < 0)
+               return ret;
+       cp += ret;
+       ret = read_pem_bignum(cp, end, &p->e, NULL);
+       if (ret < 0)
+               goto release_n;
+       cp += ret;
+       ret = read_pem_bignum(cp, end, &p->d, NULL);
+       if (ret < 0)
+               goto release_e;
+       cp += ret;
+       ret = read_pem_bignum(cp, end, &p->p, NULL);
+       if (ret < 0)
+               goto release_d;
+       cp += ret;
+       ret = read_pem_bignum(cp, end, &p->q, NULL);
+       if (ret < 0)
+               goto release_p;
+       cp += ret;
+       ret = read_pem_bignum(cp, end, &p->u, NULL);
+       if (ret < 0)
+               goto release_q;
+       return bits;
+release_q:
+       gcry_mpi_release(p->q);
+release_p:
+       gcry_mpi_release(p->p);
+release_d:
+       gcry_mpi_release(p->d);
+release_e:
+       gcry_mpi_release(p->e);
+release_n:
+       gcry_mpi_release(p->n);
+       return ret;
+}
+
+static int find_pem_bignum_offset(const unsigned char *data, int len)
 {
        const unsigned char *p = data, *end = data + len;
 
@@ -445,211 +278,173 @@ static int find_privkey_bignum_offset(const unsigned char *data, int len)
        return p - data;
 }
 
-/** Private keys start with this header. */
-#define PRIVATE_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
-/** Private keys end with this footer. */
-#define PRIVATE_KEY_FOOTER "-----END RSA PRIVATE KEY-----"
-
-static int get_private_key(const char *key_file, struct asymmetric_key **result)
+static int read_openssh_bignum(unsigned char *start, unsigned char *end,
+               gcry_mpi_t *bn, unsigned *bitsp)
 {
-       gcry_mpi_t n = NULL, e = NULL, d = NULL, p = NULL, q = NULL,
-               u = NULL;
-       unsigned char *blob, *cp, *end;
-       int blob_size, ret, n_size;
        gcry_error_t gret;
-       size_t erroff;
-       gcry_sexp_t sexp;
-       struct asymmetric_key *key;
+       size_t nscanned;
+       unsigned bits;
 
-       ret = decode_key(key_file, PRIVATE_KEY_HEADER, PRIVATE_KEY_FOOTER,
-               &blob);
-       if (ret < 0)
-               return ret;
-       blob_size = ret;
-       end = blob + blob_size;
-       ret = find_privkey_bignum_offset(blob, blob_size);
-       if (ret < 0)
-               goto free_blob;
-       PARA_INFO_LOG("reading RSA params at offset %d\n", ret);
-       cp = blob + ret;
+       gret = gcry_mpi_scan(bn, GCRYMPI_FMT_SSH, start, end - start, &nscanned);
+       if (gret) {
+               PARA_ERROR_LOG("gcry_mpi_scan: %s\n",
+                       gcry_strerror(gcry_err_code(gret)));
+               return -E_MPI_SCAN;
+       }
+       bits = (nscanned - 4 - (start[4] == '\0')) * 8;
+       if (bitsp)
+               *bitsp = bits;
+       PARA_DEBUG_LOG("scanned %u-bit bignum\n", bits);
+       return nscanned;
+}
 
-       ret = read_bignum(cp, end, &n, &n_size);
+static int read_openssh_rsa_params(unsigned char *start, unsigned char *end,
+               struct rsa_params *p)
+{
+       unsigned char *cp = start;
+       unsigned bits;
+       int ret;
+
+       ret = read_openssh_bignum(cp, end, &p->n, &bits);
        if (ret < 0)
-               goto free_blob;
+               return ret;
        cp += ret;
-
-       ret = read_bignum(cp, end, &e, NULL);
+       ret = read_openssh_bignum(cp, end, &p->e, NULL);
        if (ret < 0)
                goto release_n;
        cp += ret;
-
-       ret = read_bignum(cp, end, &d, NULL);
+       ret = read_openssh_bignum(cp, end, &p->d, NULL);
        if (ret < 0)
                goto release_e;
        cp += ret;
-
-       ret = read_bignum(cp, end, &p, NULL);
+       ret = read_openssh_bignum(cp, end, &p->u, NULL);
        if (ret < 0)
                goto release_d;
        cp += ret;
-
-       ret = read_bignum(cp, end, &q, NULL);
+       ret = read_openssh_bignum(cp, end, &p->p, NULL);
        if (ret < 0)
-               goto release_p;
+               goto release_u;
        cp += ret;
-       ret = read_bignum(cp, end, &u, NULL);
+       ret = read_openssh_bignum(cp, end, &p->q, NULL);
        if (ret < 0)
-               goto release_q;
-       /*
-        * OpenSSL uses slightly different parameters than gcrypt. To use these
-        * parameters we need to swap the values of p and q and recompute u.
-        */
-       if (gcry_mpi_cmp(p, q) > 0) {
-               gcry_mpi_swap(p, q);
-               gcry_mpi_invm(u, p, q);
-       }
-       gret = gcry_sexp_build(&sexp, &erroff, RSA_PRIVKEY_SEXP,
-               n, e, d, p, q, u);
-
-       if (gret) {
-               PARA_ERROR_LOG("offset %zu: %s\n", erroff,
-                       gcry_strerror(gcry_err_code(gret)));
-               ret = -E_SEXP_BUILD;
-               goto release_u;
-       }
-       key = para_malloc(sizeof(*key));
-       key->sexp = sexp;
-       *result = key;
-       ret = n_size * 8;
-       PARA_INFO_LOG("succesfully read %d bit private key\n", ret);
-release_u:
-       gcry_mpi_release(u);
-release_q:
-       gcry_mpi_release(q);
+               goto release_p;
+       return bits;
 release_p:
-       gcry_mpi_release(p);
+       gcry_mpi_release(p->p);
+release_u:
+       gcry_mpi_release(p->u);
 release_d:
-       gcry_mpi_release(d);
+       gcry_mpi_release(p->d);
 release_e:
-       gcry_mpi_release(e);
+       gcry_mpi_release(p->e);
 release_n:
-       gcry_mpi_release(n);
-free_blob:
-       free(blob);
+       gcry_mpi_release(p->n);
        return ret;
 }
 
-/** Public keys start with this header. */
-#define PUBLIC_KEY_HEADER "-----BEGIN PUBLIC KEY-----"
-/** Public keys end with this footer. */
-#define PUBLIC_KEY_FOOTER "-----END PUBLIC KEY-----"
-
-static int get_asn_public_key(const char *key_file, struct asymmetric_key **result)
+static int get_private_key(const char *key_file, struct asymmetric_key **result)
 {
-       gcry_mpi_t n = NULL, e = NULL;
-       unsigned char *blob, *cp, *end;
-       int blob_size, ret, n_size;
+       struct rsa_params params;
+       unsigned char *blob, *end;
+       unsigned bits;
+       int ret, key_type;
        gcry_error_t gret;
-       size_t erroff;
+       size_t erroff, blob_size;
        gcry_sexp_t sexp;
        struct asymmetric_key *key;
 
-       ret = decode_key(key_file, PUBLIC_KEY_HEADER, PUBLIC_KEY_FOOTER,
-               &blob);
+       *result = NULL;
+       ret = decode_private_key(key_file, &blob, &blob_size);
        if (ret < 0)
                return ret;
-       blob_size = ret;
+       key_type = ret;
        end = blob + blob_size;
-       ret = find_pubkey_bignum_offset(blob, blob_size);
+       if (key_type == PKT_PEM)
+               ret = find_pem_bignum_offset(blob, blob_size);
+       else
+               ret = find_openssh_bignum_offset(blob, blob_size);
        if (ret < 0)
                goto free_blob;
-       PARA_DEBUG_LOG("decoding public RSA params at offset %d\n", ret);
-       cp = blob + ret;
-
-       ret = read_bignum(cp, end, &n, &n_size);
+       PARA_INFO_LOG("reading RSA params at offset %d\n", ret);
+       if (key_type == PKT_PEM)
+               ret = read_pem_rsa_params(blob + ret, end, &params);
+       else
+               ret = read_openssh_rsa_params(blob + ret, end, &params);
        if (ret < 0)
                goto free_blob;
-       cp += ret;
-
-       ret = read_bignum(cp, end, &e, NULL);
-       if (ret < 0)
-               goto release_n;
+       bits = ret;
+       /*
+        * OpenSSL uses slightly different parameters than gcrypt. To use these
+        * parameters we need to swap the values of p and q and recompute u.
+        */
+       if (gcry_mpi_cmp(params.p, params.q) > 0) {
+               gcry_mpi_swap(params.p, params.q);
+               gcry_mpi_invm(params.u, params.p, params.q);
+       }
+       gret = gcry_sexp_build(&sexp, &erroff, RSA_PRIVKEY_SEXP, params.n,
+               params.e, params.d, params.p, params.q, params.u);
 
-       gret = gcry_sexp_build(&sexp, &erroff, RSA_PUBKEY_SEXP, n, e);
        if (gret) {
                PARA_ERROR_LOG("offset %zu: %s\n", erroff,
                        gcry_strerror(gcry_err_code(gret)));
                ret = -E_SEXP_BUILD;
-               goto release_e;
+               goto free_params;
        }
        key = para_malloc(sizeof(*key));
        key->sexp = sexp;
-       key->num_bytes = n_size;
        *result = key;
-       ret = n_size;
-       PARA_INFO_LOG("successfully read %d bit asn public key\n", n_size * 8);
+       ret = bits;
+       PARA_INFO_LOG("succesfully read %d bit private key\n", ret);
+free_params:
+       gcry_mpi_release(params.n);
+       gcry_mpi_release(params.e);
+       gcry_mpi_release(params.d);
+       gcry_mpi_release(params.u);
+       gcry_mpi_release(params.p);
+       gcry_mpi_release(params.q);
 
-release_e:
-       gcry_mpi_release(e);
-release_n:
-       gcry_mpi_release(n);
 free_blob:
        free(blob);
        return ret;
 }
 
-static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result)
+int apc_get_pubkey(const char *key_file, struct asymmetric_key **result)
 {
+       unsigned char *blob, *p, *end;
        int ret;
        gcry_error_t gret;
-       unsigned char *blob = NULL, *p, *end;
-       size_t nr_scanned, erroff, decoded_size;
-       gcry_mpi_t e = NULL, n = NULL;
+       size_t erroff, decoded_size;
+       gcry_mpi_t e, n;
+       gcry_sexp_t sexp;
+       struct asymmetric_key *key;
+       unsigned bits;
 
-       PARA_DEBUG_LOG("decoding %d byte public rsa-ssh key\n", size);
-       ret = uudecode((char *)data, size, (char **)&blob, &decoded_size);
+       ret = decode_public_key(key_file, &blob, &decoded_size);
        if (ret < 0)
-               goto free_blob;
-       end = blob + decoded_size;
-       dump_buffer("decoded key", blob, decoded_size);
-       ret = check_ssh_key_header(blob, decoded_size);
-       if (ret < 0)
-               goto free_blob;
+               return ret;
        p = blob + ret;
-       ret = -E_SSH_PARSE;
-       if (p >= end)
-               goto free_blob;
+       end = blob + decoded_size;
        PARA_DEBUG_LOG("scanning modulus and public exponent\n");
-       gret = gcry_mpi_scan(&e, GCRYMPI_FMT_SSH, p, end - p, &nr_scanned);
-       if (gret) {
-               ret = -E_MPI_SCAN;
-               PARA_CRIT_LOG("%s\n", gcry_strerror(gcry_err_code(gret)));
+       ret = read_openssh_bignum(p, end, &e, NULL);
+       if (ret < 0)
                goto free_blob;
-       }
-       PARA_DEBUG_LOG("scanned e (%zu bytes)\n", nr_scanned);
-//     gcry_mpi_aprint(GCRYMPI_FMT_HEX, &buf, NULL, rsa_e);
-//     PARA_CRIT_LOG("e: %s\n", buf);
-       p += nr_scanned;
-       if (p >= end)
-               goto release_e;
-       gret = gcry_mpi_scan(&n, GCRYMPI_FMT_SSH, p, end - p, &nr_scanned);
-       if (gret) {
-               ret = -E_MPI_SCAN;
-               PARA_ERROR_LOG("%s\n", gcry_strerror(gcry_err_code(gret)));
+       p += ret;
+       ret = read_openssh_bignum(p, end, &n, &bits);
+       if (ret < 0)
                goto release_e;
-       }
-       PARA_DEBUG_LOG("scanned n (%zu bytes)\n", nr_scanned);
-//     gcry_mpi_aprint(GCRYMPI_FMT_HEX, &buf, NULL, rsa_n);
-//     PARA_CRIT_LOG("n: %s\n", buf);
-       gret = gcry_sexp_build(result, &erroff, RSA_PUBKEY_SEXP, n, e);
+       gret = gcry_sexp_build(&sexp, &erroff, RSA_PUBKEY_SEXP, n, e);
        if (gret) {
                PARA_ERROR_LOG("offset %zu: %s\n", erroff,
                        gcry_strerror(gcry_err_code(gret)));
                ret = -E_SEXP_BUILD;
                goto release_n;
        }
-       ret = nr_scanned / 32 * 32;
-       PARA_INFO_LOG("successfully read %d bit ssh public key\n", ret * 8);
+       PARA_INFO_LOG("successfully read %u bit ssh public key\n", bits);
+       key = para_malloc(sizeof(*key));
+       key->num_bytes = ret;
+       key->sexp = sexp;
+       *result = key;
+       ret = bits;
 release_n:
        gcry_mpi_release(n);
 release_e:
@@ -659,48 +454,7 @@ free_blob:
        return ret;
 }
 
-int get_asymmetric_key(const char *key_file, int private,
-               struct asymmetric_key **result)
-{
-       int ret, ret2;
-       void *map;
-       size_t map_size;
-       unsigned char *start, *end;
-       gcry_sexp_t sexp;
-       struct asymmetric_key *key;
-
-       if (private)
-               return get_private_key(key_file, result);
-       ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL);
-       if (ret < 0)
-               return ret;
-       ret = is_ssh_rsa_key(map, map_size);
-       if (!ret) {
-               ret = para_munmap(map, map_size);
-               if (ret < 0)
-                       return ret;
-               return get_asn_public_key(key_file, result);
-       }
-       start = map + ret;
-       end = map + map_size;
-       ret = -E_SSH_PARSE;
-       if (start >= end)
-               goto unmap;
-       ret = get_ssh_public_key(start, end - start, &sexp);
-       if (ret < 0)
-               goto unmap;
-       key = para_malloc(sizeof(*key));
-       key->num_bytes = ret;
-       key->sexp = sexp;
-       *result = key;
-unmap:
-       ret2 = para_munmap(map, map_size);
-       if (ret >= 0 && ret2 < 0)
-               ret = ret2;
-       return ret;
-}
-
-void free_asymmetric_key(struct asymmetric_key *key)
+void apc_free_pubkey(struct asymmetric_key *key)
 {
        if (!key)
                return;
@@ -708,78 +462,27 @@ void free_asymmetric_key(struct asymmetric_key *key)
        free(key);
 }
 
-static int decode_rsa(gcry_sexp_t sexp, int key_size, unsigned char *outbuf,
-               size_t *nbytes)
+static int decode_rsa(gcry_sexp_t sexp, unsigned char *outbuf, size_t *nbytes)
 {
-       int ret;
-       gcry_error_t gret;
-       unsigned char oaep_buf[512];
-       gcry_mpi_t out_mpi;
-
-       if (libgcrypt_has_oaep) {
-               const char *p = gcry_sexp_nth_data(sexp, 1, nbytes);
-
-               if (!p) {
-                       PARA_ERROR_LOG("could not get data from list\n");
-                       return -E_OEAP;
-               }
-               memcpy(outbuf, p, *nbytes);
-               return 1;
-       }
-       out_mpi = gcry_sexp_nth_mpi(sexp, 0, GCRYMPI_FMT_USG);
-       if (!out_mpi)
-               return -E_SEXP_FIND;
-       gret = gcry_mpi_print(GCRYMPI_FMT_USG, oaep_buf, sizeof(oaep_buf),
-               nbytes, out_mpi);
-       if (gret) {
-               PARA_ERROR_LOG("mpi_print: %s\n", gcrypt_strerror(gret));
-               ret = -E_MPI_PRINT;
-               goto out_mpi_release;
-       }
-       /*
-        * An oaep-encoded buffer always starts with at least one zero byte.
-        * However, leading zeroes in an mpi are omitted in the output of
-        * gcry_mpi_print() when using the GCRYMPI_FMT_USG format. The
-        * alternative, GCRYMPI_FMT_STD, does not work either because here the
-        * leading zero(es) might also be omitted, depending on the value of
-        * the second byte.
-        *
-        * To circumvent this, we shift the oaep buffer to the right. But first
-        * we check that the buffer actually started with a zero byte, i.e. that
-        * nbytes < key_size. Otherwise a decoding error occurred.
-        */
-       ret = -E_SEXP_DECRYPT;
-       if (*nbytes >= key_size)
-               goto out_mpi_release;
-       memmove(oaep_buf + key_size - *nbytes, oaep_buf, *nbytes);
-       memset(oaep_buf, 0, key_size - *nbytes);
+       const char *p = gcry_sexp_nth_data(sexp, 1, nbytes);
 
-       PARA_DEBUG_LOG("decrypted buffer before unpad (%d bytes):\n",
-               key_size);
-       dump_buffer("non-unpadded decrypted buffer", oaep_buf, key_size);
-       ret = unpad_oaep(oaep_buf, key_size, outbuf, nbytes);
-       if (ret < 0)
-               goto out_mpi_release;
-       PARA_DEBUG_LOG("decrypted buffer after unpad (%zu bytes):\n",
-               *nbytes);
-       dump_buffer("unpadded decrypted buffer", outbuf, *nbytes);
-       ret = 1;
-out_mpi_release:
-       gcry_mpi_release(out_mpi);
-       return ret;
+       if (!p)
+               return -E_RSA_DECODE;
+       memcpy(outbuf, p, *nbytes);
+       return 1;
 }
 
-int priv_decrypt(const char *key_file, unsigned char *outbuf,
+int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
                unsigned char *inbuf, int inlen)
 {
        gcry_error_t gret;
-       int ret, key_size;
+       int ret;
        struct asymmetric_key *priv;
        gcry_mpi_t in_mpi = NULL;
        gcry_sexp_t in, out, priv_key;
        size_t nbytes;
 
-       ret = check_key_file(key_file, true);
+       ret = check_private_key_file(key_file);
        if (ret < 0)
                return ret;
        PARA_INFO_LOG("decrypting %d byte input\n", inlen);
@@ -787,7 +490,6 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf,
        ret = get_private_key(key_file, &priv);
        if (ret < 0)
                return ret;
-       key_size = ret / 8;
 
        /* asymmetric key priv -> sexp priv_key */
        ret = -E_SEXP_FIND;
@@ -804,7 +506,7 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf,
                goto key_release;
        }
        /* in_mpi -> in sexp */
-       gret = gcry_sexp_build(&in, NULL, rsa_decrypt_sexp, in_mpi);
+       gret = gcry_sexp_build(&in, NULL, RSA_DECRYPT_SEXP, in_mpi);
        if (gret) {
                PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
                ret = -E_SEXP_BUILD;
@@ -818,7 +520,7 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf,
                ret = -E_SEXP_DECRYPT;
                goto in_release;
        }
-       ret = decode_rsa(out, key_size, outbuf, &nbytes);
+       ret = decode_rsa(out, outbuf, &nbytes);
        if (ret < 0)
                goto out_release;
        PARA_INFO_LOG("successfully decrypted %zu byte message\n", nbytes);
@@ -832,11 +534,12 @@ in_mpi_release:
 key_release:
        gcry_sexp_release(priv_key);
 free_key:
-       free_asymmetric_key(priv);
+       gcry_sexp_release(priv->sexp);
+       free(priv);
        return ret;
 }
 
-int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
+int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
                unsigned len, unsigned char *outbuf)
 {
        gcry_error_t gret;
@@ -851,18 +554,7 @@ int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
        pub_key = gcry_sexp_find_token(pub->sexp, "public-key", 0);
        if (!pub_key)
                return -E_SEXP_FIND;
-       if (libgcrypt_has_oaep) {
-               gret = gcry_sexp_build(&in, NULL,
-                       "(data(flags oaep)(value %b))", len, inbuf);
-       } else {
-               unsigned char padded_input[256];
-               const size_t pad_size = 256;
-               /* inbuf -> padded inbuf */
-               pad_oaep(inbuf, len, padded_input, pad_size);
-               /* padded inbuf -> in sexp */
-               gret = gcry_sexp_build(&in, NULL,
-                       "(data(flags raw)(value %b))", pad_size, padded_input);
-       }
+       gret = gcry_sexp_build(&in, NULL, "(data(flags oaep)(value %b))", len, inbuf);
        if (gret) {
                PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
                ret = -E_SEXP_BUILD;
@@ -913,33 +605,20 @@ struct stream_cipher {
        gcry_cipher_hd_t handle;
 };
 
-struct stream_cipher *sc_new(const unsigned char *data, int len,
-               bool use_aes)
+struct stream_cipher *sc_new(const unsigned char *data, int len)
 {
        gcry_error_t gret;
        struct stream_cipher *sc = para_malloc(sizeof(*sc));
 
-       if (use_aes) {
-               assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
-               gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_AES128,
-                       GCRY_CIPHER_MODE_CTR, 0);
-               assert(gret == 0);
-               gret = gcry_cipher_setkey(sc->handle, data,
-                       AES_CRT128_BLOCK_SIZE);
-               assert(gret == 0);
-               gret = gcry_cipher_setctr(sc->handle,
-                       data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE);
-               assert(gret == 0);
-               return sc;
-       }
-       gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_ARCFOUR,
-               GCRY_CIPHER_MODE_STREAM, 0);
-       if (gret) {
-               PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret));
-               free(sc);
-               return NULL;
-       }
-       gret = gcry_cipher_setkey(sc->handle, data, (size_t)len);
+       assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
+       gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_AES128,
+               GCRY_CIPHER_MODE_CTR, 0);
+       assert(gret == 0);
+       gret = gcry_cipher_setkey(sc->handle, data,
+               AES_CRT128_BLOCK_SIZE);
+       assert(gret == 0);
+       gret = gcry_cipher_setctr(sc->handle,
+               data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE);
        assert(gret == 0);
        return sc;
 }
diff --git a/ggo.c b/ggo.c
deleted file mode 100644 (file)
index 81ab464..0000000
--- a/ggo.c
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file ggo.c Function for printing help. */
-
-
-#include "para.h"
-#include "ggo.h"
-#include "version.h"
-
-/**
- * Wrapper for printf() that exits on errors.
- *
- * \param fmt Usual format string.
- *
- * \return The return value of the underlying (successful) call to vprintf(3),
- * i.e. the number of characters printed, excluding the terminating null byte.
- */
-__printf_1_2 int printf_or_die(const char *fmt, ...)
-{
-       va_list argp;
-       int ret;
-
-       va_start(argp, fmt);
-       ret = vprintf(fmt, argp);
-       va_end(argp);
-       if (ret >= 0)
-               return ret;
-       exit(EXIT_FAILURE);
-}
-
-/**
- * Print one of the two given help texts.
- *
- * \param help contains the help texts.
- * \param flags What to print, see \ref ggo_print_help_flags.
- */
-void ggo_print_help(struct ggo_help *help, unsigned flags)
-{
-       const char **p;
-
-       if (help->purpose && (flags & GPH_PRINT_NAME_PURPOSE))
-               printf_or_die("para_%s - %s\n", help->prefix, help->purpose);
-       if (help->usage && (flags & GPH_PRINT_USAGE))
-               printf_or_die("\n%s\n", help->usage);
-       if (help->description && (flags & GPH_PRINT_DESCRIPTION))
-               printf_or_die("\n%s\n", help->description);
-       printf_or_die("\n");
-       if (flags & GPH_DETAILED)
-               p = help->detailed_help;
-       else
-               p = help->short_help;
-       if (!p)
-               return;
-       for (; *p; p++)
-               printf_or_die("%s\n", *p);
-}
diff --git a/ggo.h b/ggo.h
deleted file mode 100644 (file)
index 7e52403..0000000
--- a/ggo.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file ggo.h Functions and structures for help text handling. */
-
-/**
- * Information extracted from the .cmdline.h header files.
- */
-struct ggo_help {
-       /** Program or module (receiver, filter, writer) name. */
-       const char *prefix;
-       /** Generated by gengetopt from the options in the .ggo file. */
-       const char **short_help;
-       /** Like \a short_help, plus the \a details section. */
-       const char **detailed_help;
-       /** The purpose text as specified in the ggo file. */
-       const char *purpose;
-       /** Generated by gengetopt and exported via the *.cmdline.h file. */
-       const char *usage;
-       /** The description text given in the .ggo file. */
-       const char *description;
-};
-
-/**
- * Control the output of \ref ggo_print_help().
- *
- * Any combination of these flags may be passed to ggo_print_help().
- * Note that the list of supported options is always printed.
- */
-enum ggo_print_help_flags {
-       /** Whether to print the short help or the detailed help. */
-       GPH_DETAILED = 1 << 0,
-       /** Print the program or module name and the purpose text. */
-       GPH_PRINT_NAME_PURPOSE = 1 << 1,
-       /** Print the synopsis. */
-       GPH_PRINT_USAGE = 1 << 2,
-       /** Print the description text. */
-       GPH_PRINT_DESCRIPTION = 1 << 3,
-};
-
-/** Used to print the normal help of programs (--help) */
-#define GPH_STANDARD_FLAGS \
-       (GPH_PRINT_NAME_PURPOSE | GPH_PRINT_USAGE)
-
-/** Additional information for --detailed-help. */
-#define GPH_STANDARD_FLAGS_DETAILED \
-       (GPH_STANDARD_FLAGS | GPH_DETAILED | GPH_PRINT_DESCRIPTION)
-
-/** For module help embedded in a program help. */
-#define GPH_MODULE_FLAGS 0
-
-/** Modules help with detailed descriptions. */
-#define GPH_MODULE_FLAGS_DETAILED GPH_DETAILED | GPH_PRINT_DESCRIPTION
-
-/** Make a ggo_help structure using information from the .cmdline.h file. */
-#define DEFINE_GGO_HELP(_prefix) \
-       { \
-               .prefix = #_prefix, \
-               .short_help = _prefix ## _args_info_help, \
-               .detailed_help = _prefix ## _args_info_detailed_help, \
-               .purpose = _prefix ## _args_info_purpose, \
-               .usage = _prefix ## _args_info_usage, \
-               .description = _prefix ## _args_info_description, \
-       }
-
-void ggo_print_help(struct ggo_help *help, unsigned flags);
-__printf_1_2 int printf_or_die(const char *fmt, ...);
index 926f47927c7617f0aa53cae4eeec7f42026ce9f6..8370649301906eaef68a4c30d1b1f3975283dea8 100644 (file)
@@ -1,18 +1,16 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file grab_client.c Functions for grabbing the audio stream. */
 
 #include <regex.h>
 #include <sys/types.h>
+#include <lopsub.h>
+
+#include "audiod_cmd.lsg.h"
 
 #include "para.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "grab_client.h"
 #include "audiod.h"
@@ -210,77 +208,62 @@ err:
        return ret;
 }
 
-static int gc_check_args(int argc, char **argv, struct grab_client *gc)
+static int gc_check_args(struct lls_parse_result *lpr, struct grab_client *gc)
 {
-       int i;
+       const struct lls_opt_result *r;
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strncmp(arg, "-m", 2)) {
-                       if (*(arg + 3))
-                               return -E_GC_SYNTAX;
-                       switch(*(arg + 2)) {
-                       case 's':
-                               gc->mode = GM_SLOPPY;
-                               continue;
-                       case 'a':
-                               gc->mode = GM_AGGRESSIVE;
-                               continue;
-                       case 'p':
-                               gc->mode = GM_PEDANTIC;
-                               continue;
-                       default:
-                               return -E_GC_SYNTAX;
-                       }
-               }
-               if (!strcmp(arg, "-o")) {
-                       gc->flags |= GF_ONE_SHOT;
-                       continue;
-               }
-               if (!strncmp(arg, "-p=", 3)) {
-                       gc->parent = para_strdup(arg + 3);
-                       continue;
-               }
-               if (!strncmp(arg, "-n=", 3)) {
-                       gc->name = para_strdup(arg + 3);
-                       continue;
-               }
-               return -E_GC_SYNTAX;
+       r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_MODE, lpr);
+       if (lls_opt_given(r) > 0) {
+               const char *arg = lls_string_val(0, r);
+               if (strcmp(arg, "s") == 0)
+                       gc->mode = GM_SLOPPY;
+               else if (strcmp(arg, "a") == 0)
+                       gc->mode = GM_AGGRESSIVE;
+               else if (strcmp(arg, "p") == 0)
+                       gc->mode = GM_PEDANTIC;
+               else
+                       return -E_GC_SYNTAX;
+       }
+
+       r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_ONE_SHOT, lpr);
+       if (lls_opt_given(r) > 0)
+               gc->flags |= GF_ONE_SHOT;
+
+       r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_PARENT, lpr);
+       if (lls_opt_given(r) > 0) {
+               const char *arg = lls_string_val(0, r);
+               gc->parent = para_strdup(arg);
+       }
+
+       r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_NAME, lpr);
+       if (lls_opt_given(r) > 0) {
+               const char *arg = lls_string_val(0, r);
+               gc->name = para_strdup(arg);
        }
-       if (i != argc)
-               return -E_GC_SYNTAX;
        return 1;
 }
 
 /**
- * Check the command line options and allocate a grab_client structure.
+ * Create and activate a grab client.
  *
  * \param fd The file descriptor of the client.
- * \param argc Argument count.
- * \param argv Argument vector.
+ * \param lpr The parsed command line of the grab command.
  * \param s The scheduler to register the grab client task to.
  *
- * If the command line options given by \a argc and \a argv are valid.
- * allocate a struct grab_client and initialize it with this valid
- * configuration.
- *
- * If the new grab client can be added to an existing buffer tree, activate it.
- * Otherwise, add it to the inactive list for later activation.
+ * This function semantically parses the arguments given as options to the grab
+ * command. On success it allocates a struct grab_client, associates it with
+ * the given file descriptor and activates it. If the new grab client can not
+ * be attached to an existing buffer tree node it is put into the inactive list
+ * for later activation.
  *
  * \return Standard.
  */
-int grab_client_new(int fd, int argc, char **argv, struct sched *s)
+int grab_client_new(int fd, struct lls_parse_result *lpr, struct sched *s)
 {
        int ret;
        struct grab_client *gc = para_calloc(sizeof(struct grab_client));
 
-       ret = gc_check_args(argc, argv, gc);
+       ret = gc_check_args(lpr, gc);
        if (ret < 0)
                goto err_out;
        ret = dup(fd);
index 7a752cee96052fcc7516f9e4e58a1abe252e3e9b..9bc01864a7a49354a7f3c436ec3487c2de2a0637 100644 (file)
@@ -1,10 +1,6 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file grab_client.h exported symbols from grab_client.c */
 
-int grab_client_new(int fd, int argc, char **argv, struct sched *s);
+int grab_client_new(int fd, struct lls_parse_result *lpr, struct sched *s);
 void activate_grab_clients(struct sched *s);
diff --git a/gui.c b/gui.c
index 8e83cc53c6d55f53ccdb0a9dfbb4c89f0bb7525a..d779ff864ddebfc38a34ba1a0d166b5c04089556 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file gui.c Curses-based interface for paraslash. */
 
 #include <curses.h>
 #include <locale.h>
 #include <sys/time.h>
+#include <lopsub.h>
 
-#include "gui.cmdline.h"
+#include "gui.lsg.h"
 #include "para.h"
 #include "gui.h"
+#include "lsu.h"
 #include "string.h"
 #include "ringbuffer.h"
 #include "fd.h"
 #include "list.h"
 #include "sched.h"
 #include "signal.h"
-#include "ggo.h"
 #include "version.h"
 
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
+static struct lls_parse_result *cmdline_lpr, *lpr;
+
+#define CMD_PTR (lls_cmd(0, gui_suite))
+#define OPT_RESULT(_name) (lls_opt_result(LSG_GUI_PARA_GUI_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
+#define FOR_EACH_KEY_MAP(_i) for (_i = 0; _i < OPT_GIVEN(KEY_MAP); _i++)
+
 static char *stat_content[NUM_STAT_ITEMS];
 
 static struct gui_window {
@@ -47,11 +53,8 @@ struct rb_entry {
 static struct ringbuffer *bot_win_rb;
 
 static unsigned scroll_position;
-
 static pid_t exec_pid;
-
 static int exec_fds[2] = {-1, -1};
-static struct gui_args_info conf;
 static int loglevel;
 
 /** Type of the process currently being executed. */
@@ -189,7 +192,7 @@ static bool curses_active(void)
 }
 
 /* taken from mutt */
-static char *km_keyname(int c)
+static const char *km_keyname(int c)
 {
        static char buf[10];
 
@@ -245,7 +248,7 @@ static char *km_keyname(int c)
 }
 
 /* Print given number of spaces to curses window. */
-static void add_spaces(WINDOWwin, unsigned int num)
+static void add_spaces(WINDOW *win, unsigned int num)
 {
        const char space[] = "                                ";
        const unsigned sz = sizeof(space) - 1; /* number of spaces */
@@ -261,10 +264,10 @@ static void add_spaces(WINDOW* win, unsigned int num)
 }
 
 /*
- * print aligned string to curses window. This function always prints
+ * Print aligned string to curses window. This function always prints
  * exactly len chars.
  */
-static int align_str(WINDOWwin, const char *str, unsigned int len,
+static int align_str(WINDOW *win, const char *str, unsigned int len,
                unsigned int align)
 {
        int ret, num; /* of spaces */
@@ -450,10 +453,8 @@ static void rb_add_entry(int color, char *msg)
        waddstr(bot.win, msg);
 }
 
-/*
- * print formated output to bot win and refresh
- */
-__printf_2_3 static void outputf(int color, const char* fmt,...)
+/* Print formatted output to bot win and refresh. */
+__printf_2_3 static void outputf(int color, const char *fmt,...)
 {
        char *msg;
        va_list ap;
@@ -496,8 +497,9 @@ static __printf_2_3 void curses_log(int ll, const char *fmt,...)
                vfprintf(stderr, fmt, ap);
        va_end(ap);
 }
+
 /** The log function of para_gui, always set to curses_log(). */
-__printf_2_3 void (*para_log)(int, const char*, ...) = curses_log;
+__printf_2_3 void (*para_log)(int, const char *, ...) = curses_log;
 
 /* Call endwin() to reset the terminal into non-visual mode. */
 static void shutdown_curses(void)
@@ -514,8 +516,8 @@ static void shutdown_curses(void)
        endwin();
 }
 
-/* disable curses, print a message, kill running processes and exit */
-__noreturn __printf_2_3 static void die(int exit_code, const charfmt, ...)
+/* Disable curses, print a message, kill running processes and exit. */
+__noreturn __printf_2_3 static void die(int exit_code, const char *fmt, ...)
 {
        va_list argp;
 
@@ -538,9 +540,7 @@ __noreturn __printf_2_3 static void die(int exit_code, const char* fmt, ...)
        exit(exit_code);
 }
 
-/*
- * Print stat item #i to curses window
- */
+/* Print stat item #i to curses window. */
 static void print_stat_item(int i)
 {
        char *tmp;
@@ -566,19 +566,19 @@ static int update_item(int item_num, char *buf)
        if (buf && buf[0])
                goto dup;
        switch (item_num) {
-       case SI_ARTIST:
+       case SI_artist:
                *c = para_strdup("(artist tag not set)");
                goto print;
-       case SI_TITLE:
+       case SI_title:
                *c = para_strdup("(title tag not set)");
                goto print;
-       case SI_YEAR:
+       case SI_year:
                *c = para_strdup("????");
                goto print;
-       case SI_ALBUM:
+       case SI_album:
                *c = para_strdup("(album tag not set)");
                goto print;
-       case SI_COMMENT:
+       case SI_comment:
                *c = para_strdup("(comment tag not set)");
                goto print;
        }
@@ -644,7 +644,8 @@ static int status_post_select(struct sched *s, void *context)
                if (tv_diff(&st->next_exec, now, NULL) > 0)
                        return 0;
                st->next_exec.tv_sec = now->tv_sec + 2;
-               ret = para_exec_cmdline_pid(&st->pid, conf.stat_cmd_arg, fds);
+               ret = para_exec_cmdline_pid(&st->pid,
+                       OPT_STRING_VAL(STAT_CMD), fds);
                if (ret < 0)
                        return 0;
                ret = mark_fd_nonblocking(fds[1]);
@@ -676,8 +677,8 @@ static int status_post_select(struct sched *s, void *context)
                close(st->fd);
                st->fd = -1;
                clear_all_items();
-               free(stat_content[SI_BASENAME]);
-               stat_content[SI_BASENAME] =
+               free(stat_content[SI_basename]);
+               stat_content[SI_basename] =
                        para_strdup("stat command terminated!?");
                print_all_items();
                return 0;
@@ -689,9 +690,7 @@ static int status_post_select(struct sched *s, void *context)
        return 0;
 }
 
-/*
- * init all windows
- */
+/* Initialize all windows. */
 static void init_wins(int top_lines)
 {
        int top_y = 0, bot_y = top_lines + 1, sb_y = LINES - 2,
@@ -831,12 +830,13 @@ static void check_key_map_args_or_die(void)
 {
        int i;
        char *tmp = NULL;
+       const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
 
-       for (i = 0; i < conf.key_map_given; ++i) {
+       FOR_EACH_KEY_MAP(i) {
                char *handler, *arg;
 
                free(tmp);
-               tmp = para_strdup(conf.key_map_arg[i]);
+               tmp = para_strdup(lls_string_val(i, lor));
                if (!split_key_map(tmp, &handler, &arg))
                        break;
                if (strlen(handler) != 1)
@@ -849,84 +849,49 @@ static void check_key_map_args_or_die(void)
                if (find_cmd_byname(arg) < 0)
                        break;
        }
-       if (i != conf.key_map_given)
-               die(EXIT_FAILURE, "invalid key map: %s\n", conf.key_map_arg[i]);
+       if (i != OPT_GIVEN(KEY_MAP))
+               die(EXIT_FAILURE, "invalid key map: %s\n",
+                       lls_string_val(i, lor));
        free(tmp);
 }
 
-static void parse_config_file_or_die(bool override)
-{
-       bool err;
-       char *config_file;
-       struct gui_cmdline_parser_params params = {
-               .override = override,
-               .initialize = 0,
-               .check_required = !override,
-               .check_ambiguity = 0,
-               .print_errors = 1,
-       };
-
-       if (conf.config_file_given)
-               config_file = para_strdup(conf.config_file_arg);
-       else {
-               char *home = para_homedir();
-               config_file = make_message("%s/.paraslash/gui.conf", home);
-               free(home);
-       }
-       if (!file_exists(config_file)) {
-               if (!conf.config_file_given)
-                       err = false;
-               else {
-                       PARA_EMERG_LOG("config file %s does not exist\n",
-                               config_file);
-                       err = true;
-               }
-               goto out;
-       }
-       /*
-        * When the gengetopt config file parser is called more than once, any
-        * key map arguments found in the config file are _appended_ to the old
-        * values, even though we turn on ->override. We want the new arguments
-        * to replace the old ones, so we must empty the key_map_arg array
-        * first. Unfortunately, this also clears any key map arguments given
-        * at the command line.
-        */
-       if (override) {
-               int i;
-               for (i = 0; i < conf.key_map_given; i++) {
-                       free(conf.key_map_arg[i]);
-                       conf.key_map_arg[i] = NULL;
-               }
-               conf.key_map_given = 0;
+static void parse_config_file_or_die(bool reload)
+{
+       int ret;
+       unsigned flags = MCF_DONT_FREE;
+
+       if (lpr != cmdline_lpr)
+               lls_free_parse_result(lpr, CMD_PTR);
+       lpr = cmdline_lpr;
+       if (reload)
+               flags |= MCF_OVERRIDE;
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "gui.conf", &lpr, CMD_PTR, gui_suite, flags);
+       if (ret < 0) {
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
-
-       gui_cmdline_parser_config_file(config_file, &conf, &params);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
        check_key_map_args_or_die();
-       err = false;
-out:
-       free(config_file);
-       if (err)
-               exit(EXIT_FAILURE);
-       theme_init(conf.theme_arg, &theme);
+       theme_init(OPT_STRING_VAL(THEME), &theme);
 }
 
-/* reread configuration, terminate on errors */
+/* Reread configuration, terminate on errors. */
 static void reread_conf(void)
 {
        /*
-        * gengetopt might print to stderr and exit on errors. So we have to
-        * shutdown curses first.
+        * If the reload of the config file fails, we are about to exit. In
+        * this case we print the error message to stderr rather than to the
+        * curses window.  So we have to shutdown curses first.
         */
        shutdown_curses();
-       parse_config_file_or_die(true /* override */);
+       parse_config_file_or_die(true);
        init_curses();
        print_in_bar(COLOR_MSG, "config file reloaded\n");
 }
 
-/*
- * React to various signal-related events
- */
+/* React to various signal-related events. */
 static int signal_post_select(struct sched *s, __a_unused void *context)
 {
        int ret = para_next_signal(&s->rfds);
@@ -936,6 +901,14 @@ static int signal_post_select(struct sched *s, __a_unused void *context)
        switch (ret) {
        case SIGTERM:
                die(EXIT_FAILURE, "only the good die young (caught SIGTERM)\n");
+       case SIGWINCH:
+               PARA_NOTICE_LOG("got SIGWINCH\n");
+               if (curses_active()) {
+                       shutdown_curses();
+                       init_curses();
+                       redraw_bot_win();
+               }
+               return 1;
        case SIGINT:
                return 1;
        case SIGUSR1:
@@ -1027,7 +1000,7 @@ static void input_pre_select(struct sched *s, __a_unused void *context)
                sched_min_delay(s);
 }
 
-/* read from command pipe and print data to bot window */
+/* Read from command pipe and print data to bot window. */
 static void exec_and_display(const char *file_and_args)
 {
        int ret, fds[3] = {0, 1, 1};
@@ -1061,9 +1034,7 @@ static void exec_para(const char *args)
        free(file_and_args);
 }
 
-/*
- * shutdown curses and stat pipe before executing external commands
- */
+/* Shutdown curses and stat pipe before executing external commands. */
 static void exec_external(char *file_and_args)
 {
        int fds[3] = {-1, -1, -1};
@@ -1077,17 +1048,19 @@ static void exec_external(char *file_and_args)
 static void handle_command(int c)
 {
        int i;
+       const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
+       const char *keyname = km_keyname(c);
 
        /* first check user-defined key bindings */
-       for (i = 0; i < conf.key_map_given; ++i) {
+       FOR_EACH_KEY_MAP(i) {
                char *tmp, *handler, *arg;
 
-               tmp = para_strdup(conf.key_map_arg[i]);
+               tmp = para_strdup(lls_string_val(i, lor));
                if (!split_key_map(tmp, &handler, &arg)) {
                        free(tmp);
                        return;
                }
-               if (strcmp(tmp, km_keyname(c))) {
+               if (strcmp(tmp, keyname)) {
                        free(tmp);
                        continue;
                }
@@ -1107,16 +1080,17 @@ static void handle_command(int c)
        }
        /* not found, check internal key bindings */
        for (i = 0; command_list[i].handler; i++) {
-               if (!strcmp(km_keyname(c), command_list[i].key)) {
+               if (!strcmp(keyname, command_list[i].key)) {
                        command_list[i].handler();
                        return;
                }
        }
        print_in_bar(COLOR_ERRMSG, "key '%s' is not bound, press ? for help",
-               km_keyname(c));
+               keyname);
 }
 
-static int input_post_select(__a_unused struct sched *s, __a_unused void *context)
+static int input_post_select(__a_unused struct sched *s,
+               __a_unused void *context)
 {
        int ret;
        enum exec_status exs = exec_status();
@@ -1141,14 +1115,8 @@ static int input_post_select(__a_unused struct sched *s, __a_unused void *contex
        ret = wgetch(top.win);
        if (ret == ERR)
                return 0;
-       if (ret == KEY_RESIZE) {
-               if (curses_active()) {
-                       shutdown_curses();
-                       init_curses();
-                       redraw_bot_win();
-               }
+       if (ret == KEY_RESIZE) /* already handled in signal_post_select() */
                return 0;
-       }
        if (exs == EXEC_IDLE)
                handle_command(ret);
        else if (exec_pid > 0)
@@ -1329,9 +1297,10 @@ static void com_reread_conf(void)
 static void com_help(void)
 {
        int i;
+       const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
 
-       for (i = 0; i < conf.key_map_given; ++i) {
-               char *handler, *arg, *tmp = para_strdup(conf.key_map_arg[i]);
+       FOR_EACH_KEY_MAP(i) {
+               char *handler, *arg, *tmp = para_strdup(lls_string_val(i, lor));
                const char *handler_text = "???", *desc = NULL;
 
                if (!split_key_map(tmp, &handler, &arg)) {
@@ -1415,15 +1384,6 @@ static void com_prev_theme(void)
        com_refresh();
 }
 
-__noreturn static void print_help_and_die(void)
-{
-       struct ggo_help h = DEFINE_GGO_HELP(gui);
-       bool d = conf.detailed_help_given;
-
-       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
-       exit(0);
-}
-
 static int setup_tasks_and_schedule(void)
 {
        int ret;
@@ -1431,12 +1391,7 @@ static int setup_tasks_and_schedule(void)
        struct status_task status_task = {.fd = -1};
        struct input_task input_task = {.task = NULL};
        struct signal_task *signal_task;
-       struct sched sched = {
-               .default_timeout = {
-                       .tv_sec = conf.timeout_arg  / 1000,
-                       .tv_usec = (conf.timeout_arg % 1000) * 1000,
-               },
-       };
+       struct sched sched = {.default_timeout = {.tv_sec = 1}};
 
        exec_task.task = task_register(&(struct task_info) {
                .name = "exec",
@@ -1464,6 +1419,7 @@ static int setup_tasks_and_schedule(void)
        para_install_sighandler(SIGTERM);
        para_install_sighandler(SIGCHLD);
        para_install_sighandler(SIGUSR1);
+       para_install_sighandler(SIGWINCH);
        signal_task->task = task_register(&(struct task_info) {
                .name = "signal",
                .pre_select = signal_pre_select,
@@ -1476,6 +1432,21 @@ static int setup_tasks_and_schedule(void)
        return ret;
 }
 
+static void handle_help_flags(void)
+{
+       char *help;
+
+       if (OPT_GIVEN(DETAILED_HELP))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       exit(EXIT_SUCCESS);
+}
+
 /**
  * The main function of para_gui.
  *
@@ -1503,15 +1474,31 @@ static int setup_tasks_and_schedule(void)
  */
 int main(int argc, char *argv[])
 {
-       gui_cmdline_parser(argc, argv, &conf); /* exits on errors */
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       version_handle_flag("gui", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-       parse_config_file_or_die(false /* override */);
+       int ret;
+       char *errctx;
+
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &cmdline_lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       lpr = cmdline_lpr;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       version_handle_flag("gui", OPT_GIVEN(VERSION));
+       handle_help_flags();
+       parse_config_file_or_die(false);
        bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
        setlocale(LC_CTYPE, "");
        initscr(); /* needed only once, always successful */
        init_curses();
-       return setup_tasks_and_schedule() < 0? EXIT_FAILURE : EXIT_SUCCESS;
+       ret = setup_tasks_and_schedule();
+out:
+       lls_free_parse_result(lpr, CMD_PTR);
+       if (lpr != cmdline_lpr)
+               lls_free_parse_result(cmdline_lpr, CMD_PTR);
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       }
+       return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
 }
diff --git a/gui.h b/gui.h
index 1cfd39c476f82d52aa13fcd33173a94cdbd848cd..d868581847d19072377bb777520c59ccf7725173 100644 (file)
--- a/gui.h
+++ b/gui.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file gui.h symbols used by gui and gui_theme */
 
index fb8f2f9c367101eb3b4111fe133df9c8806b72c0..6f4acf37a318834f95614ccc1365c12208048b90 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file gui_theme.c Theme definitions. */
 
@@ -34,32 +30,32 @@ static void init_theme_simple(struct gui_theme *t)
        t->dflt.bg = COLOR_BLUE;
        t->sep_char = '*';
 
-       d[SI_BASENAME].prefix = "";
-       d[SI_BASENAME].postfix = "";
-       d[SI_BASENAME].color.fg = COLOR_WHITE;
-       d[SI_BASENAME].color.bg = COLOR_BLUE;
-       d[SI_BASENAME].align = CENTER;
-       d[SI_BASENAME].x = 0;
-       d[SI_BASENAME].y = 7;
-       d[SI_BASENAME].len = 100;
-
-       d[SI_STATUS].prefix = "para_server: ";
-       d[SI_STATUS].postfix = "";
-       d[SI_STATUS].color.fg = COLOR_WHITE;
-       d[SI_STATUS].color.bg = COLOR_BLUE;
-       d[SI_STATUS].align = CENTER;
-       d[SI_STATUS].x = 0;
-       d[SI_STATUS].y = 60;
-       d[SI_STATUS].len = 50;
-
-       d[SI_AUDIOD_STATUS].prefix = "para_audiod: ";
-       d[SI_AUDIOD_STATUS].postfix = "";
-       d[SI_AUDIOD_STATUS].color.fg = COLOR_WHITE;
-       d[SI_AUDIOD_STATUS].color.bg = COLOR_BLUE;
-       d[SI_AUDIOD_STATUS].align = CENTER;
-       d[SI_AUDIOD_STATUS].x = 50;
-       d[SI_AUDIOD_STATUS].y = 60;
-       d[SI_AUDIOD_STATUS].len = 50;
+       d[SI_basename].prefix = "";
+       d[SI_basename].postfix = "";
+       d[SI_basename].color.fg = COLOR_WHITE;
+       d[SI_basename].color.bg = COLOR_BLUE;
+       d[SI_basename].align = CENTER;
+       d[SI_basename].x = 0;
+       d[SI_basename].y = 7;
+       d[SI_basename].len = 100;
+
+       d[SI_status].prefix = "para_server: ";
+       d[SI_status].postfix = "";
+       d[SI_status].color.fg = COLOR_WHITE;
+       d[SI_status].color.bg = COLOR_BLUE;
+       d[SI_status].align = CENTER;
+       d[SI_status].x = 0;
+       d[SI_status].y = 60;
+       d[SI_status].len = 50;
+
+       d[SI_audiod_status].prefix = "para_audiod: ";
+       d[SI_audiod_status].postfix = "";
+       d[SI_audiod_status].color.fg = COLOR_WHITE;
+       d[SI_audiod_status].color.bg = COLOR_BLUE;
+       d[SI_audiod_status].align = CENTER;
+       d[SI_audiod_status].x = 50;
+       d[SI_audiod_status].y = 60;
+       d[SI_audiod_status].len = 50;
 
 }
 
@@ -92,266 +88,266 @@ static void init_theme_colorful_blackness(struct gui_theme *t)
        t->dflt.fg = COLOR_MAGENTA;
 
 
-       d[SI_PLAY_TIME].prefix = "";
-       d[SI_PLAY_TIME].postfix = "";
-       d[SI_PLAY_TIME].color.fg = COLOR_CYAN;
-       d[SI_PLAY_TIME].color.bg = COLOR_BLACK;
-       d[SI_PLAY_TIME].align = CENTER;
-       d[SI_PLAY_TIME].x = 0;
-       d[SI_PLAY_TIME].y = 7;
-       d[SI_PLAY_TIME].len = 35;
-
-       d[SI_BASENAME].prefix = "";
-       d[SI_BASENAME].postfix = "";
-       d[SI_BASENAME].color.fg = COLOR_CYAN;
-       d[SI_BASENAME].color.bg = COLOR_BLACK;
-       d[SI_BASENAME].align = LEFT;
-       d[SI_BASENAME].x = 35;
-       d[SI_BASENAME].y = 7;
-       d[SI_BASENAME].len = 65;
-
-       d[SI_STATUS].prefix = "";
-       d[SI_STATUS].postfix = " ";
-       d[SI_STATUS].color.fg = COLOR_RED;
-       d[SI_STATUS].color.bg = COLOR_BLACK;
-       d[SI_STATUS].align = RIGHT;
-       d[SI_STATUS].x = 0;
-       d[SI_STATUS].y = 17;
-       d[SI_STATUS].len = 11;
-
-       d[SI_STATUS_FLAGS].prefix = "(";
-       d[SI_STATUS_FLAGS].postfix = ")";
-       d[SI_STATUS_FLAGS].color.fg = COLOR_RED;
-       d[SI_STATUS_FLAGS].color.bg = COLOR_BLACK;
-       d[SI_STATUS_FLAGS].align = LEFT;
-       d[SI_STATUS_FLAGS].x = 11;
-       d[SI_STATUS_FLAGS].y = 17;
-       d[SI_STATUS_FLAGS].len = 10;
-
-       d[SI_IMAGE_ID].prefix = "img: ";
-       d[SI_IMAGE_ID].postfix = "";
-       d[SI_IMAGE_ID].color.fg = COLOR_RED;
-       d[SI_IMAGE_ID].color.bg = COLOR_BLACK;
-       d[SI_IMAGE_ID].align = CENTER;
-       d[SI_IMAGE_ID].x = 21;
-       d[SI_IMAGE_ID].y = 17;
-       d[SI_IMAGE_ID].len = 10;
-
-       d[SI_LYRICS_ID].prefix = "lyr: ";
-       d[SI_LYRICS_ID].postfix = "";
-       d[SI_LYRICS_ID].color.fg = COLOR_RED;
-       d[SI_LYRICS_ID].color.bg = COLOR_BLACK;
-       d[SI_LYRICS_ID].align = CENTER;
-       d[SI_LYRICS_ID].x = 31;
-       d[SI_LYRICS_ID].y = 17;
-       d[SI_LYRICS_ID].len = 11;
-
-       d[SI_FORMAT].prefix = "format: ";
-       d[SI_FORMAT].postfix = "";
-       d[SI_FORMAT].color.fg = COLOR_RED;
-       d[SI_FORMAT].color.bg = COLOR_BLACK;
-       d[SI_FORMAT].align = CENTER;
-       d[SI_FORMAT].x = 42;
-       d[SI_FORMAT].y = 17;
-       d[SI_FORMAT].len = 18;
-
-       d[SI_NUM_PLAYED].prefix = "#";
-       d[SI_NUM_PLAYED].postfix = "";
-       d[SI_NUM_PLAYED].color.fg = COLOR_RED;
-       d[SI_NUM_PLAYED].color.bg = COLOR_BLACK;
-       d[SI_NUM_PLAYED].align = LEFT;
-       d[SI_NUM_PLAYED].x = 60;
-       d[SI_NUM_PLAYED].y = 17;
-       d[SI_NUM_PLAYED].len = 5;
-
-       d[SI_BITRATE].prefix = "";
-       d[SI_BITRATE].postfix = "";
-       d[SI_BITRATE].color.fg = COLOR_RED;
-       d[SI_BITRATE].color.bg = COLOR_BLACK;
-       d[SI_BITRATE].align = CENTER;
-       d[SI_BITRATE].x = 65;
-       d[SI_BITRATE].y = 17;
-       d[SI_BITRATE].len = 13;
-
-       d[SI_FREQUENCY].prefix = "";
-       d[SI_FREQUENCY].postfix = "";
-       d[SI_FREQUENCY].color.fg = COLOR_RED;
-       d[SI_FREQUENCY].color.bg = COLOR_BLACK;
-       d[SI_FREQUENCY].align = CENTER;
-       d[SI_FREQUENCY].x = 78;
-       d[SI_FREQUENCY].y = 17;
-       d[SI_FREQUENCY].len = 10;
-
-       d[SI_SCORE].prefix = "sc: ";
-       d[SI_SCORE].postfix = "";
-       d[SI_SCORE].color.fg = COLOR_RED;
-       d[SI_SCORE].color.bg = COLOR_BLACK;
-       d[SI_SCORE].align = CENTER;
-       d[SI_SCORE].x = 88;
-       d[SI_SCORE].y = 17;
-       d[SI_SCORE].len = 10;
-
-       d[SI_AUDIOD_STATUS].prefix = "";
-       d[SI_AUDIOD_STATUS].postfix = "";
-       d[SI_AUDIOD_STATUS].color.fg = COLOR_MAGENTA;
-       d[SI_AUDIOD_STATUS].color.bg = COLOR_BLACK;
-       d[SI_AUDIOD_STATUS].align = CENTER;
-       d[SI_AUDIOD_STATUS].x = 0;
-       d[SI_AUDIOD_STATUS].y = 27;
-       d[SI_AUDIOD_STATUS].len = 5;
-
-       d[SI_DECODER_FLAGS].prefix = "[";
-       d[SI_DECODER_FLAGS].postfix = "]";
-       d[SI_DECODER_FLAGS].color.fg = COLOR_MAGENTA;
-       d[SI_DECODER_FLAGS].color.bg = COLOR_BLACK;
-       d[SI_DECODER_FLAGS].align = CENTER;
-       d[SI_DECODER_FLAGS].x = 5;
-       d[SI_DECODER_FLAGS].y = 27;
-       d[SI_DECODER_FLAGS].len = 10;
-
-       d[SI_MTIME].prefix = "mod: ";
-       d[SI_MTIME].postfix = "";
-       d[SI_MTIME].color.fg = COLOR_MAGENTA;
-       d[SI_MTIME].color.bg = COLOR_BLACK;
-       d[SI_MTIME].align = CENTER;
-       d[SI_MTIME].x = 15;
-       d[SI_MTIME].y = 27;
-       d[SI_MTIME].len = 22;
-
-       d[SI_FILE_SIZE].prefix = "";
-       d[SI_FILE_SIZE].postfix = "kb";
-       d[SI_FILE_SIZE].color.fg = COLOR_MAGENTA;
-       d[SI_FILE_SIZE].color.bg = COLOR_BLACK;
-       d[SI_FILE_SIZE].align = CENTER;
-       d[SI_FILE_SIZE].x = 37;
-       d[SI_FILE_SIZE].y = 27;
-       d[SI_FILE_SIZE].len = 10;
-
-       d[SI_CHANNELS].prefix = "";
-       d[SI_CHANNELS].postfix = "ch";
-       d[SI_CHANNELS].color.fg = COLOR_MAGENTA;
-       d[SI_CHANNELS].color.bg = COLOR_BLACK;
-       d[SI_CHANNELS].align = CENTER;
-       d[SI_CHANNELS].x = 47;
-       d[SI_CHANNELS].y = 27;
-       d[SI_CHANNELS].len = 5;
-
-       d[SI_LAST_PLAYED].prefix = "lp: ";
-       d[SI_LAST_PLAYED].postfix = "";
-       d[SI_LAST_PLAYED].color.fg = COLOR_MAGENTA;
-       d[SI_LAST_PLAYED].color.bg = COLOR_BLACK;
-       d[SI_LAST_PLAYED].align = CENTER;
-       d[SI_LAST_PLAYED].x = 52;
-       d[SI_LAST_PLAYED].y = 27;
-       d[SI_LAST_PLAYED].len = 21;
-
-       d[SI_NUM_CHUNKS].prefix = "";
-       d[SI_NUM_CHUNKS].postfix = "x";
-       d[SI_NUM_CHUNKS].color.fg = COLOR_MAGENTA;
-       d[SI_NUM_CHUNKS].color.bg = COLOR_BLACK;
-       d[SI_NUM_CHUNKS].align = RIGHT;
-       d[SI_NUM_CHUNKS].x = 73;
-       d[SI_NUM_CHUNKS].y = 27;
-       d[SI_NUM_CHUNKS].len = 11;
-
-       d[SI_CHUNK_TIME].prefix = "";
-       d[SI_CHUNK_TIME].postfix = "ms";
-       d[SI_CHUNK_TIME].color.fg = COLOR_MAGENTA;
-       d[SI_CHUNK_TIME].color.bg = COLOR_BLACK;
-       d[SI_CHUNK_TIME].align = LEFT;
-       d[SI_CHUNK_TIME].x = 84;
-       d[SI_CHUNK_TIME].y = 27;
-       d[SI_CHUNK_TIME].len = 8;
-
-       d[SI_AMPLIFICATION].prefix = "amp:";
-       d[SI_AMPLIFICATION].postfix = "";
-       d[SI_AMPLIFICATION].color.fg = COLOR_MAGENTA;
-       d[SI_AMPLIFICATION].color.bg = COLOR_BLACK;
-       d[SI_AMPLIFICATION].align = RIGHT;
-       d[SI_AMPLIFICATION].x = 92;
-       d[SI_AMPLIFICATION].y = 27;
-       d[SI_AMPLIFICATION].len = 8;
-
-       d[SI_TECHINFO].prefix = "";
-       d[SI_TECHINFO].postfix = "";
-       d[SI_TECHINFO].color.fg = COLOR_GREEN;
-       d[SI_TECHINFO].color.bg = COLOR_BLACK;
-       d[SI_TECHINFO].align = CENTER;
-       d[SI_TECHINFO].x = 0;
-       d[SI_TECHINFO].y = 43;
-       d[SI_TECHINFO].len = 100;
-
-       d[SI_TITLE].prefix = "";
-       d[SI_TITLE].postfix = ",";
-       d[SI_TITLE].color.fg = COLOR_GREEN;
-       d[SI_TITLE].color.bg = COLOR_BLACK;
-       d[SI_TITLE].align = RIGHT;
-       d[SI_TITLE].x = 0;
-       d[SI_TITLE].y = 53;
-       d[SI_TITLE].len = 45;
-
-       d[SI_ARTIST].prefix = " by ";
-       d[SI_ARTIST].postfix = "";
-       d[SI_ARTIST].color.fg = COLOR_GREEN;
-       d[SI_ARTIST].color.bg = COLOR_BLACK;
-       d[SI_ARTIST].align = LEFT;
-       d[SI_ARTIST].x = 45;
-       d[SI_ARTIST].y = 53;
-       d[SI_ARTIST].len = 45;
-
-       d[SI_YEAR].prefix = "(";
-       d[SI_YEAR].postfix = ")";
-       d[SI_YEAR].color.fg = COLOR_GREEN;
-       d[SI_YEAR].color.bg = COLOR_BLACK;
-       d[SI_YEAR].align = RIGHT;
-       d[SI_YEAR].x = 90;
-       d[SI_YEAR].y = 53;
-       d[SI_YEAR].len = 10;
-
-       d[SI_ALBUM].prefix = "A: ";
-       d[SI_ALBUM].postfix = ",";
-       d[SI_ALBUM].color.fg = COLOR_GREEN;
-       d[SI_ALBUM].color.bg = COLOR_BLACK;
-       d[SI_ALBUM].align = RIGHT;
-       d[SI_ALBUM].x = 0;
-       d[SI_ALBUM].y = 63;
-       d[SI_ALBUM].len = 50;
-
-       d[SI_COMMENT].prefix = " C: ";
-       d[SI_COMMENT].postfix = "";
-       d[SI_COMMENT].color.fg = COLOR_GREEN;
-       d[SI_COMMENT].color.bg = COLOR_BLACK;
-       d[SI_COMMENT].align = LEFT;
-       d[SI_COMMENT].x = 50;
-       d[SI_COMMENT].y = 63;
-       d[SI_COMMENT].len = 50;
-
-       d[SI_AFS_MODE].prefix = "";
-       d[SI_AFS_MODE].postfix = "";
-       d[SI_AFS_MODE].color.fg = COLOR_YELLOW;
-       d[SI_AFS_MODE].color.bg = COLOR_BLACK;
-       d[SI_AFS_MODE].align = CENTER;
-       d[SI_AFS_MODE].x = 0;
-       d[SI_AFS_MODE].y = 77;
-       d[SI_AFS_MODE].len = 100;
-
-       d[SI_ATTRIBUTES_TXT].prefix = "";
-       d[SI_ATTRIBUTES_TXT].postfix = "";
-       d[SI_ATTRIBUTES_TXT].color.fg = COLOR_YELLOW;
-       d[SI_ATTRIBUTES_TXT].color.bg = COLOR_BLACK;
-       d[SI_ATTRIBUTES_TXT].align = CENTER;
-       d[SI_ATTRIBUTES_TXT].x = 0;
-       d[SI_ATTRIBUTES_TXT].y = 87;
-       d[SI_ATTRIBUTES_TXT].len = 100;
-
-       d[SI_DIRECTORY].prefix = "dir: ";
-       d[SI_DIRECTORY].postfix = "";
-       d[SI_DIRECTORY].color.fg = COLOR_YELLOW;
-       d[SI_DIRECTORY].color.bg = COLOR_BLACK;
-       d[SI_DIRECTORY].align = CENTER;
-       d[SI_DIRECTORY].x = 0;
-       d[SI_DIRECTORY].y = 97;
-       d[SI_DIRECTORY].len = 100;
+       d[SI_play_time].prefix = "";
+       d[SI_play_time].postfix = "";
+       d[SI_play_time].color.fg = COLOR_CYAN;
+       d[SI_play_time].color.bg = COLOR_BLACK;
+       d[SI_play_time].align = CENTER;
+       d[SI_play_time].x = 0;
+       d[SI_play_time].y = 7;
+       d[SI_play_time].len = 35;
+
+       d[SI_basename].prefix = "";
+       d[SI_basename].postfix = "";
+       d[SI_basename].color.fg = COLOR_CYAN;
+       d[SI_basename].color.bg = COLOR_BLACK;
+       d[SI_basename].align = LEFT;
+       d[SI_basename].x = 35;
+       d[SI_basename].y = 7;
+       d[SI_basename].len = 65;
+
+       d[SI_status].prefix = "";
+       d[SI_status].postfix = " ";
+       d[SI_status].color.fg = COLOR_RED;
+       d[SI_status].color.bg = COLOR_BLACK;
+       d[SI_status].align = RIGHT;
+       d[SI_status].x = 0;
+       d[SI_status].y = 17;
+       d[SI_status].len = 11;
+
+       d[SI_status_flags].prefix = "(";
+       d[SI_status_flags].postfix = ")";
+       d[SI_status_flags].color.fg = COLOR_RED;
+       d[SI_status_flags].color.bg = COLOR_BLACK;
+       d[SI_status_flags].align = LEFT;
+       d[SI_status_flags].x = 11;
+       d[SI_status_flags].y = 17;
+       d[SI_status_flags].len = 10;
+
+       d[SI_image_id].prefix = "img: ";
+       d[SI_image_id].postfix = "";
+       d[SI_image_id].color.fg = COLOR_RED;
+       d[SI_image_id].color.bg = COLOR_BLACK;
+       d[SI_image_id].align = CENTER;
+       d[SI_image_id].x = 21;
+       d[SI_image_id].y = 17;
+       d[SI_image_id].len = 10;
+
+       d[SI_lyrics_id].prefix = "lyr: ";
+       d[SI_lyrics_id].postfix = "";
+       d[SI_lyrics_id].color.fg = COLOR_RED;
+       d[SI_lyrics_id].color.bg = COLOR_BLACK;
+       d[SI_lyrics_id].align = CENTER;
+       d[SI_lyrics_id].x = 31;
+       d[SI_lyrics_id].y = 17;
+       d[SI_lyrics_id].len = 11;
+
+       d[SI_format].prefix = "format: ";
+       d[SI_format].postfix = "";
+       d[SI_format].color.fg = COLOR_RED;
+       d[SI_format].color.bg = COLOR_BLACK;
+       d[SI_format].align = CENTER;
+       d[SI_format].x = 42;
+       d[SI_format].y = 17;
+       d[SI_format].len = 16;
+
+       d[SI_num_played].prefix = "#";
+       d[SI_num_played].postfix = "";
+       d[SI_num_played].color.fg = COLOR_RED;
+       d[SI_num_played].color.bg = COLOR_BLACK;
+       d[SI_num_played].align = LEFT;
+       d[SI_num_played].x = 58;
+       d[SI_num_played].y = 17;
+       d[SI_num_played].len = 5;
+
+       d[SI_bitrate].prefix = "";
+       d[SI_bitrate].postfix = "";
+       d[SI_bitrate].color.fg = COLOR_RED;
+       d[SI_bitrate].color.bg = COLOR_BLACK;
+       d[SI_bitrate].align = CENTER;
+       d[SI_bitrate].x = 65;
+       d[SI_bitrate].y = 17;
+       d[SI_bitrate].len = 13;
+
+       d[SI_frequency].prefix = "";
+       d[SI_frequency].postfix = "";
+       d[SI_frequency].color.fg = COLOR_RED;
+       d[SI_frequency].color.bg = COLOR_BLACK;
+       d[SI_frequency].align = CENTER;
+       d[SI_frequency].x = 78;
+       d[SI_frequency].y = 17;
+       d[SI_frequency].len = 10;
+
+       d[SI_score].prefix = "sc: ";
+       d[SI_score].postfix = "";
+       d[SI_score].color.fg = COLOR_RED;
+       d[SI_score].color.bg = COLOR_BLACK;
+       d[SI_score].align = CENTER;
+       d[SI_score].x = 88;
+       d[SI_score].y = 17;
+       d[SI_score].len = 10;
+
+       d[SI_audiod_status].prefix = "";
+       d[SI_audiod_status].postfix = "";
+       d[SI_audiod_status].color.fg = COLOR_MAGENTA;
+       d[SI_audiod_status].color.bg = COLOR_BLACK;
+       d[SI_audiod_status].align = CENTER;
+       d[SI_audiod_status].x = 0;
+       d[SI_audiod_status].y = 27;
+       d[SI_audiod_status].len = 5;
+
+       d[SI_decoder_flags].prefix = "[";
+       d[SI_decoder_flags].postfix = "]";
+       d[SI_decoder_flags].color.fg = COLOR_MAGENTA;
+       d[SI_decoder_flags].color.bg = COLOR_BLACK;
+       d[SI_decoder_flags].align = CENTER;
+       d[SI_decoder_flags].x = 5;
+       d[SI_decoder_flags].y = 27;
+       d[SI_decoder_flags].len = 10;
+
+       d[SI_mtime].prefix = "mod: ";
+       d[SI_mtime].postfix = "";
+       d[SI_mtime].color.fg = COLOR_MAGENTA;
+       d[SI_mtime].color.bg = COLOR_BLACK;
+       d[SI_mtime].align = CENTER;
+       d[SI_mtime].x = 15;
+       d[SI_mtime].y = 27;
+       d[SI_mtime].len = 22;
+
+       d[SI_file_size].prefix = "";
+       d[SI_file_size].postfix = "kb";
+       d[SI_file_size].color.fg = COLOR_MAGENTA;
+       d[SI_file_size].color.bg = COLOR_BLACK;
+       d[SI_file_size].align = CENTER;
+       d[SI_file_size].x = 37;
+       d[SI_file_size].y = 27;
+       d[SI_file_size].len = 10;
+
+       d[SI_channels].prefix = "";
+       d[SI_channels].postfix = "ch";
+       d[SI_channels].color.fg = COLOR_MAGENTA;
+       d[SI_channels].color.bg = COLOR_BLACK;
+       d[SI_channels].align = CENTER;
+       d[SI_channels].x = 47;
+       d[SI_channels].y = 27;
+       d[SI_channels].len = 5;
+
+       d[SI_last_played].prefix = "lp: ";
+       d[SI_last_played].postfix = "";
+       d[SI_last_played].color.fg = COLOR_MAGENTA;
+       d[SI_last_played].color.bg = COLOR_BLACK;
+       d[SI_last_played].align = CENTER;
+       d[SI_last_played].x = 52;
+       d[SI_last_played].y = 27;
+       d[SI_last_played].len = 21;
+
+       d[SI_num_chunks].prefix = "";
+       d[SI_num_chunks].postfix = "x";
+       d[SI_num_chunks].color.fg = COLOR_MAGENTA;
+       d[SI_num_chunks].color.bg = COLOR_BLACK;
+       d[SI_num_chunks].align = RIGHT;
+       d[SI_num_chunks].x = 73;
+       d[SI_num_chunks].y = 27;
+       d[SI_num_chunks].len = 11;
+
+       d[SI_chunk_time].prefix = "";
+       d[SI_chunk_time].postfix = "ms";
+       d[SI_chunk_time].color.fg = COLOR_MAGENTA;
+       d[SI_chunk_time].color.bg = COLOR_BLACK;
+       d[SI_chunk_time].align = LEFT;
+       d[SI_chunk_time].x = 84;
+       d[SI_chunk_time].y = 27;
+       d[SI_chunk_time].len = 8;
+
+       d[SI_amplification].prefix = "amp:";
+       d[SI_amplification].postfix = "";
+       d[SI_amplification].color.fg = COLOR_MAGENTA;
+       d[SI_amplification].color.bg = COLOR_BLACK;
+       d[SI_amplification].align = RIGHT;
+       d[SI_amplification].x = 92;
+       d[SI_amplification].y = 27;
+       d[SI_amplification].len = 8;
+
+       d[SI_techinfo].prefix = "";
+       d[SI_techinfo].postfix = "";
+       d[SI_techinfo].color.fg = COLOR_GREEN;
+       d[SI_techinfo].color.bg = COLOR_BLACK;
+       d[SI_techinfo].align = CENTER;
+       d[SI_techinfo].x = 0;
+       d[SI_techinfo].y = 43;
+       d[SI_techinfo].len = 100;
+
+       d[SI_title].prefix = "";
+       d[SI_title].postfix = ",";
+       d[SI_title].color.fg = COLOR_GREEN;
+       d[SI_title].color.bg = COLOR_BLACK;
+       d[SI_title].align = RIGHT;
+       d[SI_title].x = 0;
+       d[SI_title].y = 53;
+       d[SI_title].len = 45;
+
+       d[SI_artist].prefix = " by ";
+       d[SI_artist].postfix = "";
+       d[SI_artist].color.fg = COLOR_GREEN;
+       d[SI_artist].color.bg = COLOR_BLACK;
+       d[SI_artist].align = LEFT;
+       d[SI_artist].x = 45;
+       d[SI_artist].y = 53;
+       d[SI_artist].len = 45;
+
+       d[SI_year].prefix = "(";
+       d[SI_year].postfix = ")";
+       d[SI_year].color.fg = COLOR_GREEN;
+       d[SI_year].color.bg = COLOR_BLACK;
+       d[SI_year].align = RIGHT;
+       d[SI_year].x = 90;
+       d[SI_year].y = 53;
+       d[SI_year].len = 10;
+
+       d[SI_album].prefix = "A: ";
+       d[SI_album].postfix = ",";
+       d[SI_album].color.fg = COLOR_GREEN;
+       d[SI_album].color.bg = COLOR_BLACK;
+       d[SI_album].align = RIGHT;
+       d[SI_album].x = 0;
+       d[SI_album].y = 63;
+       d[SI_album].len = 50;
+
+       d[SI_comment].prefix = " C: ";
+       d[SI_comment].postfix = "";
+       d[SI_comment].color.fg = COLOR_GREEN;
+       d[SI_comment].color.bg = COLOR_BLACK;
+       d[SI_comment].align = LEFT;
+       d[SI_comment].x = 50;
+       d[SI_comment].y = 63;
+       d[SI_comment].len = 50;
+
+       d[SI_afs_mode].prefix = "";
+       d[SI_afs_mode].postfix = "";
+       d[SI_afs_mode].color.fg = COLOR_YELLOW;
+       d[SI_afs_mode].color.bg = COLOR_BLACK;
+       d[SI_afs_mode].align = CENTER;
+       d[SI_afs_mode].x = 0;
+       d[SI_afs_mode].y = 77;
+       d[SI_afs_mode].len = 100;
+
+       d[SI_attributes_txt].prefix = "";
+       d[SI_attributes_txt].postfix = "";
+       d[SI_attributes_txt].color.fg = COLOR_YELLOW;
+       d[SI_attributes_txt].color.bg = COLOR_BLACK;
+       d[SI_attributes_txt].align = CENTER;
+       d[SI_attributes_txt].x = 0;
+       d[SI_attributes_txt].y = 87;
+       d[SI_attributes_txt].len = 100;
+
+       d[SI_directory].prefix = "dir: ";
+       d[SI_directory].postfix = "";
+       d[SI_directory].color.fg = COLOR_YELLOW;
+       d[SI_directory].color.bg = COLOR_BLACK;
+       d[SI_directory].align = CENTER;
+       d[SI_directory].x = 0;
+       d[SI_directory].y = 97;
+       d[SI_directory].len = 100;
 }
 
 struct theme_description {
index 2f334787200e4a0134df6a108a0c4dc8a7dd0775..1fb60bad810d86dbbb0098a3714c2caa3d9e835b 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file http_recv.c paraslash's http receiver */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "recv_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "http.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "recv.h"
-#include "http_recv.cmdline.h"
 #include "net.h"
 #include "string.h"
 #include "fd.h"
@@ -30,7 +26,7 @@
 /**
  * the possible states of a http receiver node
  *
- * \sa receiver_node
+ * \sa \ref receiver_node.
  */
 enum http_recv_status {HTTP_CONNECTED, HTTP_SENT_GET_REQUEST, HTTP_STREAMING};
 
@@ -46,7 +42,7 @@ struct private_http_recv_data {
         * It gets initialized to \p HTTP_CONNECTED by the open function of the
         * http receiver.
         *
-        * \sa receiver::open, receiver_node.
+        * \sa \ref receiver::open, \ref receiver_node.
         */
        enum http_recv_status status;
 };
@@ -110,8 +106,10 @@ static int http_recv_post_select(struct sched *s, void *context)
        }
        if (phd->status == HTTP_SENT_GET_REQUEST) {
                ret = read_pattern(rn->fd, HTTP_OK_MSG, strlen(HTTP_OK_MSG), &s->rfds);
-               if (ret < 0)
+               if (ret < 0) {
+                       PARA_ERROR_LOG("did not receive HTTP OK message\n");
                        goto out;
+               }
                if (ret == 0)
                        return 0;
                PARA_INFO_LOG("received ok msg, streaming\n");
@@ -132,8 +130,10 @@ static int http_recv_post_select(struct sched *s, void *context)
                btr_add_output_pool(rn->btrp, num_bytes - iov[0].iov_len, btrn);
        }
 out:
-       if (ret < 0)
+       if (ret < 0) {
+               PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
                btr_remove_node(&rn->btrn);
+       }
        return ret;
 }
 
@@ -144,20 +144,13 @@ static void http_recv_close(struct receiver_node *rn)
        free(rn->private_data);
 }
 
-static void *http_recv_parse_config(int argc, char **argv)
-{
-       struct http_recv_args_info *tmp = para_calloc(sizeof(*tmp));
-
-       http_recv_cmdline_parser(argc, argv, tmp);
-       return tmp;
-}
-
 static int http_recv_open(struct receiver_node *rn)
 {
        struct private_http_recv_data *phd;
-       struct http_recv_args_info *conf = rn->conf;
-       int fd, ret = para_connect_simple(IPPROTO_TCP, conf->host_arg,
-                                                      conf->port_arg);
+       struct lls_parse_result *lpr = rn->lpr;
+       const char *r_i = RECV_CMD_OPT_STRING_VAL(HTTP, HOST, lpr);
+       uint32_t r_p = RECV_CMD_OPT_UINT32_VAL(HTTP, PORT, lpr);
+       int fd, ret = para_connect_simple(IPPROTO_TCP, r_i, r_p);
 
        if (ret < 0)
                return ret;
@@ -174,30 +167,9 @@ static int http_recv_open(struct receiver_node *rn)
        return 1;
 }
 
-static void http_recv_free_config(void *conf)
-{
-       http_recv_cmdline_parser_free(conf);
-       free(conf);
-}
-
-/**
- * The init function of the http receiver.
- *
- * \param r Pointer to the receiver struct to initialize.
- *
- * This initializes all function pointers of \a r.
- */
-void http_recv_init(struct receiver *r)
-{
-       struct http_recv_args_info dummy;
-
-       http_recv_cmdline_parser_init(&dummy);
-       r->open = http_recv_open;
-       r->close = http_recv_close;
-       r->pre_select = http_recv_pre_select;
-       r->post_select = http_recv_post_select;
-       r->parse_config = http_recv_parse_config;
-       r->free_config = http_recv_free_config;
-       r->help = (struct ggo_help)DEFINE_GGO_HELP(http_recv);
-       http_recv_cmdline_parser_free(&dummy);
-}
+const struct receiver lsg_recv_cmd_com_http_user_data = {
+       .open = http_recv_open,
+       .close = http_recv_close,
+       .pre_select = http_recv_pre_select,
+       .post_select = http_recv_post_select,
+};
index 9d0f49aee03cd6b30d6a73e9eda74dcb9be4cfc0..c6b9decca941489c82b629982ddb6d6dbdb2ea33 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file http_send.c paraslash's http sender */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "server.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "string.h"
-#include "server.cmdline.h"
 #include "afh.h"
+#include "net.h"
 #include "server.h"
 #include "http.h"
 #include "list.h"
 #include "sched.h"
 #include "vss.h"
 #include "close_on_fork.h"
-#include "net.h"
 #include "fd.h"
 #include "chunk_queue.h"
-#include "acl.h"
 
 /** Message sent to clients that do not send a valid get request. */
 #define HTTP_ERR_MSG "HTTP/1.0 400 Bad Request\n"
@@ -80,6 +76,13 @@ static void http_shutdown_clients(void)
        shutdown_clients(hss);
 }
 
+static void http_shutdown(void)
+{
+       http_shutdown_clients();
+       generic_acl_deplete(&hss->acl);
+       free_sender_status(hss);
+}
+
 static int queue_chunk_or_shutdown(struct sender_client *sc,
                struct sender_status *ss, const char *buf, size_t num_bytes)
 {
@@ -196,10 +199,13 @@ static void http_post_select(fd_set *rfds, __a_unused fd_set *wfds)
 static void http_pre_select(int *max_fileno, fd_set *rfds, fd_set *wfds)
 {
        struct sender_client *sc, *tmp;
+       unsigned n;
 
-       if (hss->listen_fd < 0)
-               return;
-       para_fd_set(hss->listen_fd, rfds, max_fileno);
+       FOR_EACH_LISTEN_FD(n, hss) {
+               if (hss->listen_fds[n] < 0)
+                       continue;
+               para_fd_set(hss->listen_fds[n], rfds, max_fileno);
+       }
        list_for_each_entry_safe(sc, tmp, &hss->client_list, node) {
                struct private_http_sender_data *phsd = sc->private_data;
                if (phsd->status == HTTP_CONNECTED) /* need to recv get request */
@@ -212,7 +218,8 @@ static void http_pre_select(int *max_fileno, fd_set *rfds, fd_set *wfds)
 
 static int http_com_on(__a_unused struct sender_command_data *scd)
 {
-       return generic_com_on(hss, IPPROTO_TCP);
+       generic_com_on(hss, IPPROTO_TCP);
+       return 1;
 }
 
 static int http_com_off(__a_unused struct sender_command_data *scd)
@@ -238,37 +245,45 @@ static char *http_status(void)
        return generic_sender_status(hss, "http");
 }
 
-/**
- * The init function of the http sender.
- *
- * \param s Pointer to the http sender struct.
- *
- * It initializes all function pointers of \a s, the client list and the access
- * control list. If the autostart option was given, the tcp port is opened.
+/*
+ * Initialize the client list and the access control list, and optionally
+ * listen on the tcp port.
  */
-void http_send_init(struct sender *s)
+static void http_send_init(void)
 {
-       int ret;
-       s->status = http_status;
-       s->send = http_send;
-       s->pre_select = http_pre_select;
-       s->post_select = http_post_select;
-       s->shutdown_clients = http_shutdown_clients;
-       s->resolve_target = NULL;
-       s->help = generic_sender_help;
-       s->client_cmds[SENDER_on] = http_com_on;
-       s->client_cmds[SENDER_off] = http_com_off;
-       s->client_cmds[SENDER_deny] = http_com_deny;
-       s->client_cmds[SENDER_allow] = http_com_allow;
-       s->client_cmds[SENDER_add] = NULL;
-       s->client_cmds[SENDER_delete] = NULL;
-
-       init_sender_status(hss, conf.http_access_arg, conf.http_access_given,
-               conf.http_port_arg, conf.http_max_clients_arg,
-               conf.http_default_deny_given);
-       if (conf.http_no_autostart_given)
+       init_sender_status(hss, OPT_RESULT(HTTP_ACCESS),
+               OPT_RESULT(HTTP_LISTEN_ADDRESS),
+               OPT_UINT32_VAL(HTTP_PORT), OPT_UINT32_VAL(HTTP_MAX_CLIENTS),
+               OPT_GIVEN(HTTP_DEFAULT_DENY));
+       if (OPT_GIVEN(HTTP_NO_AUTOSTART))
                return;
-       ret = generic_com_on(hss, IPPROTO_TCP);
-       if (ret < 0)
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       generic_com_on(hss, IPPROTO_TCP);
 }
+
+/**
+ * The HTTP sender.
+ *
+ * This is the only sender which does not FEC-encode the stream. This is not
+ * necessary because HTTP sits on top of TCP, a reliable transport which
+ * retransmits lost packets automatically. The sender employs per-client queues
+ * which queue chunks of audio data if they can not be sent immediately because
+ * the write operation would block. Most methods of the sender are implemented
+ * as wrappers for the generic functions defined in \ref send_common.c.
+ */
+const struct sender http_sender = {
+       .name = "http",
+       .init = http_send_init,
+       .shutdown = http_shutdown,
+       .pre_select = http_pre_select,
+       .post_select = http_post_select,
+       .send = http_send,
+       .shutdown_clients = http_shutdown_clients,
+       .client_cmds = {
+               [SENDER_on] = http_com_on,
+               [SENDER_off] = http_com_off,
+               [SENDER_deny] = http_com_deny,
+               [SENDER_allow] = http_com_allow,
+       },
+       .help = generic_sender_help,
+       .status = http_status,
+};
diff --git a/imdct.c b/imdct.c
index d2a0681898b6497f2742e842d3b9ef1ed104621a..93577b5451a0cbb8c8da58e997a9f4d77179243e 100644 (file)
--- a/imdct.c
+++ b/imdct.c
@@ -7,8 +7,7 @@
  * Copyright (c) 2002 Fabrice Bellard
  * Partly based on libdjbfft by D. J. Bernstein
  *
- * Licensed under the GNU Lesser General Public License.
- * For licencing details see COPYING.LIB.
+ * Licensed under the GNU Lesser General Public License, see file COPYING.LIB.
  */
 
 /**
@@ -141,28 +140,26 @@ __a_const static int split_radix_permutation(int i, int n)
 }
 
 /* z[0...8n - 1], w[1...2n - 1] */
-#define PASS(name)\
-static void name(struct fft_complex *z, const fftsample_t *wre, unsigned int n)\
-{\
-       fftsample_t t1, t2, t3, t4, t5, t6;\
-       int o1 = 2 * n;\
-       int o2 = 4 * n;\
-       int o3 = 6 * n;\
-       const fftsample_t *wim = wre + o1;\
-       n--;\
-\
-       TRANSFORM_ZERO(z[0], z[o1], z[o2], z[o3]);\
-       TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]);\
-       do {\
-               z += 2;\
-               wre += 2;\
-               wim -= 2;\
-               TRANSFORM(z[0], z[o1], z[o2], z[o3], wre[0], wim[0]);\
-               TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]);\
-       } while (--n);\
+static void pass(struct fft_complex *z, const fftsample_t *wre, unsigned int n)
+{
+       fftsample_t t1, t2, t3, t4, t5, t6;
+       int o1 = 2 * n;
+       int o2 = 4 * n;
+       int o3 = 6 * n;
+       const fftsample_t *wim = wre + o1;
+
+       n--;
+       TRANSFORM_ZERO(z[0], z[o1], z[o2], z[o3]);
+       TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]);
+       do {
+               z += 2;
+               wre += 2;
+               wim -= 2;
+               TRANSFORM(z[0], z[o1], z[o2], z[o3], wre[0], wim[0]);
+               TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]);
+       } while (--n);
 }
 
-PASS(pass)
 #undef BUTTERFLIES
 #define BUTTERFLIES BUTTERFLIES_BIG
 
index c34a0f6eb3c5ff3fd6fcb0e85546a180d21fd880..a8197308e8323c8fe78546117cc2e9479df0fbfd 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file interactive.c Readline abstraction for interactive sessions. */
 
@@ -206,26 +202,7 @@ void i9e_attach_to_stdout(struct btr_node *producer)
 
 static void wipe_bottom_line(void)
 {
-       char x[] = "          ";
-       int n = i9ep->num_columns;
-
-       /*
-        * For reasons beyond my understanding, writing more than 68 characters
-        * here causes MacOS to mess up the terminal. Writing a line of spaces
-        * in smaller chunks works fine though. Weird.
-        */
-       fprintf(i9ep->stderr_stream, "\r");
-       while (n > 0) {
-               if (n >= sizeof(x)) {
-                       fprintf(i9ep->stderr_stream, "%s", x);
-                       n -= sizeof(x);
-                       continue;
-               }
-               x[n] = '\0';
-               fprintf(i9ep->stderr_stream, "%s", x);
-               break;
-       }
-       fprintf(i9ep->stderr_stream, "\r");
+       fprintf(i9ep->stderr_stream, "\r%s\r", i9ep->empty_line);
 }
 
 #ifndef RL_FREE_KEYMAP_DECLARED
@@ -785,17 +762,25 @@ int i9e_print_completions(struct i9e_completer *completers)
        ci.argc = create_argv(ci.buffer, " ", &ci.argv);
        ci.word_num = compute_word_num(ci.buffer, " ", ci.point);
 
+       /* determine the current word to complete */
        end = ci.buffer + ci.point;
+
+       if (*end == ' ') {
+               if (ci.point == 0 || ci.buffer[ci.point - 1] == ' ') {
+                       ci.word = para_strdup(NULL);
+                       goto create_matches;
+               } else /* The cursor is positioned right after a word */
+                       end--;
+       }
        for (p = end; p > ci.buffer && *p != ' '; p--)
                ; /* nothing */
        if (*p == ' ')
                p++;
-
        n = end - p + 1;
        ci.word = para_malloc(n + 1);
        strncpy(ci.word, p, n);
        ci.word[n] = '\0';
-
+create_matches:
        PARA_DEBUG_LOG("line: %s, point: %d (%c), wordnum: %d, word: %s\n",
                ci.buffer, ci.point, ci.buffer[ci.point], ci.word_num, ci.word);
        if (ci.word_num == 0)
index e6d53dee81ec13853a403cbfbedbf68b43accb2d..40ff294018b221708dd0d1db9cac23c80fb70533 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file interactive.h Public API for interactive sessions. */
 
@@ -33,7 +29,7 @@ struct i9e_completion_result {
  *
  * \param name Determines the name of the function to be defined.
  */
-#define I9E_DUMMY_COMPLETER(name) void name ## _completer( \
+#define I9E_DUMMY_COMPLETER(name) static void name ## _completer( \
                __a_unused struct i9e_completion_info *ciname, \
                struct i9e_completion_result *result) {result->matches = NULL;}
 
diff --git a/ipc.c b/ipc.c
index a9d7351ce6ddf1a416e2419440857cb3cee70eb9..8e9dd51a2369e7e60ebd4bda3fa889eaed892248 100644 (file)
--- a/ipc.c
+++ b/ipc.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file ipc.c Inter-process communication and shared memory helpers. */
 
@@ -67,7 +63,7 @@ static void para_semop(int id, struct sembuf *sops, int num)
  *
  * This function either succeeds or aborts.
  *
- * \sa semop(2), struct misc_meta_data.
+ * \sa semop(2), struct \ref misc_meta_data.
  */
 void mutex_lock(int id)
 {
@@ -93,7 +89,7 @@ void mutex_lock(int id)
  *
  * This function either succeeds or aborts.
  *
- * \sa semop(2), struct misc_meta_data.
+ * \sa semop(2), struct \ref misc_meta_data.
  */
 void mutex_unlock(int id)
 {
@@ -160,6 +156,27 @@ int shm_attach(int id, enum shm_attach_mode mode, void **result)
        return *result == (void *) -1? -ERRNO_TO_PARA_ERROR(errno) : 1;
 }
 
+/**
+ * Get the size of a shared memory segment.
+ *
+ * \param id The shared memory segment identifier.
+ * \param result Size in bytes is returned here, zero on errors.
+ *
+ * \return Standard.
+ *
+ * \sa shmctl(2).
+ */
+int shm_size(int id, size_t *result)
+{
+       struct shmid_ds ds; /* data structure */
+
+       *result = 0;
+       if (shmctl(id, IPC_STAT, &ds) < 0)
+               return -ERRNO_TO_PARA_ERROR(errno);
+       *result = ds.shm_segsz;
+       return 1;
+}
+
 /**
  * Detach a shared memory segment.
  *
diff --git a/ipc.h b/ipc.h
index c8d31c0c991641a1f71627c855705bdf7decd5c6..c44371045cdfb7a4313068a5e91bfb22fd366a84 100644 (file)
--- a/ipc.h
+++ b/ipc.h
@@ -11,4 +11,5 @@ int shm_new(size_t size);
 int shm_attach(int id, enum shm_attach_mode mode, void **result);
 int shm_detach(void *addr);
 int shm_destroy(int id);
+int shm_size(int id, size_t *result);
 size_t shm_get_shmmax(void);
diff --git a/lsu.c b/lsu.c
new file mode 100644 (file)
index 0000000..8ccf80d
--- /dev/null
+++ b/lsu.c
@@ -0,0 +1,235 @@
+/* Copyright (C) 2018 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file lsu.c Utilities related to the lopsub library. */
+
+#include <lopsub.h>
+#include <regex.h>
+
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "lsu.h"
+#include "fd.h"
+
+static int lsu_lopsub_error(int ret, char **errctx, char **result, unsigned *num_chars)
+{
+       const char *se = para_strerror(-ret);
+       unsigned n;
+
+       if (*errctx)
+               n = xasprintf(result, "%s: %s\n", *errctx, se);
+       else
+               n = xasprintf(result, "lopsub error: %s\n", se);
+       free(*errctx);
+       *errctx = NULL;
+       if (num_chars)
+               *num_chars = n;
+       return ret;
+}
+
+static void lsu_get_subcommand_summary(bool long_summary,
+               const struct lls_suite *suite,
+               const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
+               char **result, unsigned *num_chars)
+{
+       int i;
+       const struct lls_command *cmd;
+       const char *name, *aux_info = NULL;
+       struct para_buffer pb = {.max_size = 0 /* unlimited */};
+
+       para_printf(&pb, "Available subcommands:\n");
+       if (long_summary) {
+               int maxname = 0, max_aux_info = 0;
+               for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
+                       maxname = PARA_MAX(maxname,
+                               (int)strlen(lls_command_name(cmd)));
+                       if (aux_info_cb) {
+                               aux_info = aux_info_cb(i, false);
+                               if (!aux_info)
+                                       continue;
+                               max_aux_info = PARA_MAX(max_aux_info,
+                                       (int)strlen(aux_info));
+                       }
+               }
+               for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
+                       if (aux_info_cb)
+                               aux_info = aux_info_cb(i, false);
+                       para_printf(&pb, "%-*s %-*s %s\n", maxname,
+                               lls_command_name(cmd), max_aux_info, aux_info?
+                               aux_info : "", lls_purpose(cmd));
+               }
+       } else {
+               unsigned n = 8;
+               para_printf(&pb, "\t");
+               for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
+                       name = lls_command_name(cmd);
+                       if (i > 1)
+                               n += para_printf(&pb, ", ");
+                       if (n > 70) {
+                               para_printf(&pb, "\n\t");
+                               n = 8;
+                       }
+                       n += para_printf(&pb, "%s", name);
+               }
+               para_printf(&pb, "\n");
+       }
+       *result = pb.buf;
+       if (num_chars)
+               *num_chars = pb.offset;
+}
+
+/**
+ * A generic implementation of the help subcommand.
+ *
+ * This function returns the help text for the given subcommand, or the list of
+ * all subcommands if no non-option argument is given. The function is generic
+ * in that it works for arbitrary lopsub suites.
+ *
+ * \param long_help Applies to both command list and command help.
+ * \param suite The supercommand, if any, is omitted.
+ * \param lpr Used to determine whether a non-option argument is given.
+ * \param aux_info_cb Optional callback, may return NULL, static memory.
+ * \param result Must be freed by the caller.
+ * \param num_chars Initialized to the length of the returned string, optional.
+ *
+ * If the optional aux_info_cb function pointer is not NULL, the callback
+ * function must return the string representation of the aux_info structure of
+ * the given command, or NULL to indicate that this command has no aux info
+ * structure.
+ *
+ * The function fails if lpr has more than one non-option argument, or if there
+ * is exactly one non-option argument, but this argument is not the name of a
+ * subcommand in the given lopsub suite.
+ *
+ * \return Standard. In the failure case a suitable error message is returned
+ * via the result pointer and num_chars is set accordingly.
+ */
+int lsu_com_help(bool long_help, const struct lls_parse_result *lpr,
+               const struct lls_suite *suite,
+               const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
+               char **result, unsigned *num_chars)
+{
+       int ret;
+       unsigned n;
+       char *errctx, *tmp;
+       const char *arg, *aux_info = NULL;
+       const struct lls_command *cmd;
+
+       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
+       if (ret < 0)
+               return lsu_lopsub_error(ret, &errctx, result, num_chars);
+       if (lls_num_inputs(lpr) == 0) {
+               lsu_get_subcommand_summary(long_help, suite,
+                       aux_info_cb, result, num_chars);
+               return 0;
+       }
+       arg = lls_input(0, lpr);
+       ret = lls(lls_lookup_subcmd(arg, suite, &errctx));
+       if (ret < 0)
+               return lsu_lopsub_error(ret, &errctx, result, num_chars);
+       cmd = lls_cmd(ret, suite);
+       tmp = long_help? lls_long_help(cmd) : lls_short_help(cmd);
+       if (aux_info_cb)
+               aux_info = aux_info_cb(ret, true);
+       n = xasprintf(result, "%s%s%s", tmp, aux_info? aux_info : "",
+               aux_info? "\n" : "");
+       free(tmp);
+       if (num_chars)
+               *num_chars = n;
+       return 1;
+}
+
+/**
+ * Merge command line options and config file options.
+ *
+ * This function parses the options stored in the configuration file and merges
+ * them with the currently effective options. If the application supports
+ * config files, it is supposed to call this after the command line options
+ * have been parsed. If the application also supports config file reloading,
+ * the function will be called for that purpose.
+ *
+ * \param path Config file path, usually the argument to --config-file.
+ * \param dflt Relative to ~/.paraslash, ignored if path is not NULL.
+ * \param lpr Value-result pointer.
+ * \param cmd Passed to lls_parse() and lls_merge().
+ * \param suite Needed to tell whether cmd is the supercommand.
+ * \param flags See enum \ref lsu_merge_cf_flags.
+ *
+ * The function does nothing if path is NULL and the default config file does
+ * not exist, or if path is an empty file. Otherwise, the options of the config
+ * file are parsed, the parse result is merged with lpr, and the merged parse
+ * result is returned via lpr.
+ *
+ * By default, lpr is freed if the merge was done, but this can be changed by
+ * including MCF_DONT_FREE flags.
+ *
+ * \return Zero if there was nothing to do, one if the config file options were
+ * merged successfully, negative error code on failure. It is considered an error
+ * if path is given, but the file does not exist.
+ */
+int lsu_merge_config_file_options(const char *path, const char *dflt,
+               struct lls_parse_result **lpr, const struct lls_command *cmd,
+               const struct lls_suite *suite, unsigned flags)
+{
+       int ret;
+       void *map;
+       size_t sz;
+       int cf_argc;
+       char *cf, **cf_argv, *errctx = NULL;
+       struct lls_parse_result *old_lpr = *lpr, *cf_lpr, *merged_lpr;
+       const char *subcmd_name;
+
+       if (path)
+               cf = para_strdup(path);
+       else {
+               char *home = para_homedir();
+               cf = make_message("%s/.paraslash/%s", home, dflt);
+               free(home);
+       }
+       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       if (ret < 0) {
+               if (ret == -E_EMPTY)
+                       ret = 0;
+               else if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && !path)
+                       ret = 0;
+               else
+                       PARA_ERROR_LOG("failed to mmap config file %s\n", cf);
+               goto free_cf;
+       }
+       subcmd_name = (lls_cmd(0, suite) == cmd)? NULL : lls_command_name(cmd);
+       ret = lls(lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx));
+       para_munmap(map, sz);
+       if (ret < 0) {
+               PARA_ERROR_LOG("failed to convert config file %s\n", cf);
+               goto lopsub_error;
+       }
+       cf_argc = ret;
+       ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
+       lls_free_argv(cf_argv);
+       if (ret < 0) {
+               PARA_ERROR_LOG("failed to parse config file %s\n", cf);
+               goto lopsub_error;
+       }
+       if (flags & MCF_OVERRIDE)
+               ret = lls(lls_merge(cf_lpr, old_lpr, cmd, &merged_lpr, &errctx));
+       else
+               ret = lls(lls_merge(old_lpr, cf_lpr, cmd, &merged_lpr, &errctx));
+       lls_free_parse_result(cf_lpr, cmd);
+       if (ret < 0) {
+               PARA_ERROR_LOG("could not merge options in %s\n", cf);
+               goto lopsub_error;
+       }
+       if (!(flags & MCF_DONT_FREE))
+               lls_free_parse_result(old_lpr, cmd);
+       *lpr = merged_lpr;
+       ret = 1;
+       goto free_cf;
+lopsub_error:
+       assert(ret < 0);
+       if (errctx)
+               PARA_ERROR_LOG("lopsub error: %s\n", errctx);
+       free(errctx);
+free_cf:
+       free(cf);
+       return ret;
+}
diff --git a/lsu.h b/lsu.h
new file mode 100644 (file)
index 0000000..6141dfc
--- /dev/null
+++ b/lsu.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2018 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file lsu.h Lopsub Utilities, enums and public functions. */
+int lsu_com_help(bool long_help, const struct lls_parse_result *lpr,
+               const struct lls_suite *suite,
+               const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
+               char **result, unsigned *num_chars);
+
+/** Flags for \ref lsu_merge_config_file_options(). */
+enum lsu_merge_cf_flags {
+       /**
+        * Whether the options specified in the configuration file should
+        * override the currently effective options. At application startup
+        * this is usually unset so that command line options take precedence
+        * over config file options. However, if the application supports
+        * re-reading the configuration, it can make sense to enable this flag.
+        */
+       MCF_OVERRIDE = 1,
+       /**
+        * After the two lopsub parse results have been merged, the merged
+        * parse result usually becomes the effective configuration and the
+        * parse result which corresponds to the former effective options is no
+        * longer needed. Therefore \ref lsu_merge_config_file_options() frees
+        * this former parse result by default. This flag instructs the
+        * function to keep it. This is mostly useful if the application
+        * supports re-reading the config file so that the parse result which
+        * corresponds to the command line options is kept for future calls to
+        * \ref lsu_merge_config_file_options().
+        */
+       MCF_DONT_FREE = 2,
+};
+
+int lsu_merge_config_file_options(const char *path, const char *dflt,
+               struct lls_parse_result **lpr, const struct lls_command *cmd,
+               const struct lls_suite *suite, unsigned flags);
diff --git a/m4/gengetopt/afh.m4 b/m4/gengetopt/afh.m4
deleted file mode 100644 (file)
index 9b8a650..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-args "--unamed-opts=audio_file --no-handle-version --no-handle-help"
-
-purpose "Print information about audio file(s)"
-
-include(header.m4)
-include(loglevel.m4)
-
-<qu>
-
-###################################
-section "printing meta information"
-###################################
-
-option "chunk-table" c
-#~~~~~~~~~~~~~~~~~~~~~
-"print also the chunk table"
-flag off
-details = "
-       The 'chunk table' of an audio file is an array of offsets
-       within the audio file. Each offset corresponds to chunk
-       of encoded data. The exact meaning of 'chunk' depends on
-       the audio format.
-
-       Programs which are unaware of the particular audio format can
-       read the chunk table to obtain the timing information needed
-       to stream the file.
-"
-
-option "parser-friendly" p
-#~~~~~~~~~~~~~~~~~~~~~~~~~
-"do not use human-readable output format"
-flag off
-details = "
-       Currently this option only affects the format of the chunk table,
-       so it has no effect if --chunk-table is not given.
-
-       The human-readable output (the default) consists of one output
-       line per chunk and the output contains also the chunk number,
-       the duration and the size of each chunk. The parser-friendly
-       output prints only the offsets, in one line.
-"
-
-#############################
-section "modifying meta tags"
-#############################
-
-option "modify" m
-#~~~~~~~~~~~~~~~~
-"modify (rather than print) tags"
-flag off
-details = "
-       When this option is given, para_afh creates the result file
-       as a temporary copy of the given file(s), but with meta
-       tags changed according to the options below. On errors,
-       the temporary file is removed, leaving the original file
-       unchanged. On success, if --backup is given, the original
-       file is moved away. Finally the temporary file is renamed to
-       the name of the original file.
-"
-
-option "backup" b
-"create backup of the original file"
-flag off
-details = "
-       The backup suffix is '~', i.e. a single tilde character is appended
-       to the given file name.
-"
-
-option "year" y
-#~~~~~~~~~~~~~~
-"set the year tag"
-string typestr="year"
-optional
-
-option "title" t
-#~~~~~~~~~~~~~~~
-"set the title tag"
-string typestr="title"
-optional
-
-option "artist" a
-#~~~~~~~~~~~~~~~~
-"set the artist/author tag"
-string typestr="artist"
-optional
-
-option "album" A
-#~~~~~~~~~~~~~~~
-"set the album tag"
-string typestr="album"
-optional
-
-option "comment" C
-#~~~~~~~~~~~~~~~~~
-"set the comment tag"
-string typestr="comment"
-optional
-
-</qu>
diff --git a/m4/gengetopt/afh_recv.m4 b/m4/gengetopt/afh_recv.m4
deleted file mode 100644 (file)
index 28a0a9e..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Make an audio stream from a local file"
-
-description "
-       The afh (audio format handler) receiver can be used to write
-       selected parts of the given audio file without decoding
-       the data.
-
-       The selected parts of the content of the audio file are passed
-       to the child nodes of the buffer tree. Only complete chunks
-       with respect of the underlying audio format are passed.
-"
-
-include(header.m4)
-<qu>
-option "filename" f
-#~~~~~~~~~~~~~~~~~~
-"file to open"
-string typestr = "filename"
-required
-
-option "begin-chunk" b
-#~~~~~~~~~~~~~~~~~~~~~
-"skip the beginning of the file"
-int typestr = "chunk_num"
-default = "0"
-optional
-details = "
-       The chunk_num argument must be between -num_chunks and
-       num_chunks - 1, inclusively, where num_chunks is the total
-       number of chunks of the audio file given by the argument to
-       --filename. If chunk_num is negative, the given number of
-       chunks are counted backwards from the end of the file. For
-       example --begin-chunk -100 instructs the afh receiver to
-       start output at chunk num_chunks - 100. This is useful for
-       selecting the last part of an audio file.
-"
-
-option "end-chunk" e
-#~~~~~~~~~~~~~~~~~~~
-"only write up to chunk chunk_num"
-int typestr = "chunk_num"
-optional
-details = "
-       For the chunk_num argument the same rules as for --begin-chunk
-       apply. The default is to write up to the last chunk.
-"
-
-option "just-in-time" j
-#~~~~~~~~~~~~~~~~~~~~~~
-"use timed writes"
-flag off
-details = "
-       Write the specified chunks of data 'just in time', i.e. the
-       write of each chunk is delayed until the time it is needed
-       by the decoder/player in order to guarantee an uninterrupted
-       audio stream. This may be useful for third-party software
-       that is capable of reading from stdin.
-"
-
-option "no-header" H
-#~~~~~~~~~~~~~~~~~~~
-"do not write an audio file header"
-flag off
-details = "
-       If an audio format needs information about the audio file
-       in a format-specific header in order to be understood by
-       the decoding software, a suitable header is automatically
-       send. This option changes the default behaviour, i.e. no
-       header is written.
-"
-
-</qu>
diff --git a/m4/gengetopt/alsa_write.m4 b/m4/gengetopt/alsa_write.m4
deleted file mode 100644 (file)
index b2c5621..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Native ALSA output plugin"
-
-include(header.m4)
-
-<qu>
-option "device" d
-#~~~~~~~~~~~~~~~~
-"set PCM device"
-string typestr = "device"
-default = "default"
-optional
-details = "
-       Check for the presence of a /proc/asound/ directory to see if
-       ALSA is present in your kernel. The file /proc/asound/devices
-       contains all devices ALSA knows about.
-"
-
-option "buffer-time" B
-#~~~~~~~~~~~~~~~~~~~~~
-"duration of the ALSA buffer"
-int typestr = "milliseconds"
-default = "170"
-optional
-details = "
-       This is only a hint as ALSA might pick a slightly different
-       time, depending on the sound hardware. The chosen value is
-       shown in debug output as BUFFER_TIME.
-
-       If synchronization between multiple clients is desired,
-       the same buffer time should be configured for all clients.
-"
-
-</qu>
-
diff --git a/m4/gengetopt/amp_filter.m4 b/m4/gengetopt/amp_filter.m4
deleted file mode 100644 (file)
index a02cc5b..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Amplify the decoded audio stream"
-
-option "amp" a
-#~~~~~~~~~~~~~
-"amplification value"
-int typestr="number"
-default="32"
-optional
-details="
-       The amplification value determines the scaling factor by
-       which the amplitude of the audio stream is multiplied. The
-       formula for the scaling factor is
-
-               factor = 1 + amp / 64.
-
-       For example, an amplifiction value of zero results in a
-       scaling factor of one while an amplification value of 64
-       means to double the volume.
-"
diff --git a/m4/gengetopt/ao_write.m4 b/m4/gengetopt/ao_write.m4
deleted file mode 100644 (file)
index 29112d7..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Output plugin for libao"
-
-include(header.m4)
-<qu>
-
-option "driver" d
-#~~~~~~~~~~~~~~~~
-"Select a output driver by name"
-string typestr = "name"
-optional
-details = "
-       If this is not given, the driver with the highest priority
-       (see below) will be used.
-"
-
-option "ao-option" o
-#~~~~~~~~~~~~~~~~~~~
-"Pass a key-value pair to the libao driver"
-string typestr = "key:value"
-optional
-multiple
-details = "
-       For each time this option is given, the supplied key-value
-       pair is appended to the list of options for the driver. Invalid
-       keys are silently ignored.
-"
-
-</qu>
diff --git a/m4/gengetopt/audioc.m4 b/m4/gengetopt/audioc.m4
deleted file mode 100644 (file)
index f216204..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-args "--unamed-opts=command --conf-parser --no-handle-version --no-handle-help"
-
-purpose "Communicate with para_audiod through a local socket"
-
-include(header.m4)
-<qu>
-option "socket" s
-#~~~~~~~~~~~~~~~~
-"well-known socket (default=/var/paraslash/audiod.socket.$HOSTNAME)"
-       string typestr="filename"
-       optional
-
-
-option "bufsize" b
-#~~~~~~~~~~~~~~~~~
-"size of internal buffer"
-       int typestr="bytes"
-       default="8192"
-       optional
-</qu>
-
-define(CURRENT_PROGRAM,para_audioc)
-define(DEFAULT_HISTORY_FILE,~/.paraslash/audioc.history)
-include(loglevel.m4)
-include(history_file.m4)
-include(complete.m4)
diff --git a/m4/gengetopt/audiod.m4 b/m4/gengetopt/audiod.m4
deleted file mode 100644 (file)
index 75c0458..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-args "--no-handle-help --no-handle-version --conf-parser"
-
-purpose "Connect to para_server, receive, decode and play audio streams"
-
-include(header.m4)
-define(CURRENT_PROGRAM,para_audiod)
-define(DEFAULT_CONFIG_FILE,~/.paraslash/audiod.conf)
-
-<qu>
-#########################
-section "General options"
-#########################
-</qu>
-
-include(loglevel.m4)
-include(color.m4)
-include(config_file.m4)
-include(logfile.m4)
-include(log_timing.m4)
-include(daemon.m4)
-include(user.m4)
-include(group.m4)
-include(priority.m4)
-
-<qu>
-########################
-section "Audiod options"
-########################
-
-option "force" F
-#~~~~~~~~~~~~~~~
-"force startup"
-flag off
-details="
-       If this flag is not given, para_audiod refuses to start if the
-       well-known socket file (see the --socket option) already exists
-       because this usually means that para_audiod is already running
-       and listening on that socket. After a crash or if para_audiod
-       received a SIGKILL signal, a stale socket file might remain and
-       you have to use --force once to force startup of para_audiod.
-"
-
-option "mode" m
-#~~~~~~~~~~~~~~
-"startup mode"
-string typestr="mode"
-default="on"
-optional
-details="
-       Para_audiod supports three modes of operation: On, off and
-       standby (sb).  This option selects the mode that should be
-       used on startup. If para_audiod operates in \"on\" mode, it
-       will connect to para_server in order to receive its status
-       information. If para_server announces the availability of an
-       audio stream, para_audiod will automatically download, decode
-       and play the audio stream according to the given stream I/O
-       options, see below.
-
-       In \"standby\" mode, para_audiod will only receive the
-       status information from para_server but will not download
-       the audio stream.
-
-       In \"off\" mode, para_audiod does not connect para_server at
-       all, but still listens on the local socket for connections.
-"
-
-option "socket" s
-#~~~~~~~~~~~~~~~~
-"well-known socket"
-string typestr="filename"
-optional
-details="
-       Para_audiod uses a \"well-known\" socket to listen
-       on for connections from para_audioc. This socket is a
-       special file in the file system; its location defaults to
-       /var/paraslash/audiod_sock.<host_name>.
-
-       para_audioc, the client program used to connect to para_audiod,
-       opens this socket in order to talk to para_audiod.  If the
-       default value for para_audiod is changed, para_audioc must be
-       instructed to use also \"filename\" for connecting para_audiod.
-"
-
-option "user-allow" -
-#~~~~~~~~~~~~~~~~~~~~
-"allow this user to connect to audiod"
-string typestr = "username"
-optional
-multiple
-details = "
-       Allow the user identified by username (either a string or
-       a UID) to connect to para_audiod. This option may be given
-       multiple times. If not specified at all, all users are allowed
-       to connect.
-
-       This feature is based on the ability to send unix
-       credentials through local sockets using ancillary data
-       (SCM_CREDENTIALS). Currently it only works on Linux. On
-       other operating systems the option is silently ignored and
-       all local users are allowed to connect.
-"
-
-option "clock-diff-count" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"sync clock on startup"
-int typestr="count"
-default="0"
-optional
-details="
-       Check the clock difference between the host running para_server
-       and the local host running para_audiod that many times before
-       starting any stream I/0. Set this to non-zero for non-local
-       setups if the clocks of these two hosts are not synchronized
-       by ntp or similar.
-"
-
-#############################
-section "Stream I/O options"
-#############################
-
-option "receiver" r
-#~~~~~~~~~~~~~~~~~~
-"select receiver"
-string typestr="receiver_spec"
-default="http"
-optional
-multiple
-details="
-       This option may be given multiple times, for each audio format
-       separately. If multiple definitions for an audio format are
-       given, the first one is selected.
-
-       The \"receiver_spec\" consists of an audio format specifier
-       and one or more receiver arguments, separated by a colon.
-
-       The audio format specifier is a regular expression which
-       specifies the set of audio formats for which this option
-       should apply.
-
-       If any receiver options are present, the whole receiver
-       argument must be quoted:
-
-               -r 'mp3:http -i my.host.org -p 8009'
-
-       Since a single dot '.' matches the name of any audio format,
-       specifying '.' instead of 'mp3' above activates the http
-       receiver for all audio formats.
-
-"
-
-option "filter" f
-#~~~~~~~~~~~~~~~~
-"Specify the filter configuration."
-string typestr = "filter_spec"
-optional
-multiple
-details = "
-       This option may be given multiple times. The \"filter_spec\"
-       consists of an audio format specifier (see above), the name
-       of the filter, and any options for that filter. Note that
-       order matters.
-
-       The compiled-in defaults apply to all audio formats for which
-       no --filter option was given. These defaults depend on the
-       receiver being used.
-
-       For HTTP streams, only the decoder for the current audio
-       format is activated. UDP and DCCP streams, on the other
-       hand, are sent FEC-encoded by para_server. In order to play
-       such streams, the receiver output must be FEC-decoded first,
-       i.e. fed to the fecdec filter. Therefore the default for UDP
-       and DCCP streams is to activate the fecdec filter, followed
-       by the decoding filter for the audio format.
-
-       Examples:
-
-               --filter 'mp3:mp3dec'
-
-               --filter 'mp3|aac:compress --inertia 5 --damp 2'
-
-               --filter '.:fecdec'
-
-"
-
-option "writer" w
-#~~~~~~~~~~~~~~~~
-"Specify stream writer."
-string typestr="writer_spec"
-optional
-multiple
-details="
-       May be given multiple times, even multiple times for the same
-       audio format.  Default value is \"alsa\" for all supported
-       audio formats. Example:
-
-               --writer 'aac|wma:oss'
-
-"
-
-option "stream-delay" -
-#~~~~~~~~~~~~~~~~~~~~~~
-"time for client sync"
-int typestr="milliseconds"
-default="200"
-optional
-details="
-       Add the given amount of milliseconds to the stream start time
-       announced by para_server and do not send data to the writer
-       before that time (modulo clock difference).
-
-       This is useful mainly for synchronizing the audio output of
-       different clients.
-"
-</qu>
diff --git a/m4/gengetopt/channels.m4 b/m4/gengetopt/channels.m4
deleted file mode 100644 (file)
index 64c2518..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<qu>
-option "channels" c
-#~~~~~~~~~~~~~~~~~~
-"specify number of channels"
-int typestr = "num"
-default = "2"
-optional
-details = "
-       It is only necessary to specify this option for raw audio. If
-       it is not given, the channel count is queried from the parent
-       buffer tree nodes (e.g. the decoder) or the wav header. Only
-       if this fails, the default value applies.
-"
-</qu>
diff --git a/m4/gengetopt/client.m4 b/m4/gengetopt/client.m4
deleted file mode 100644 (file)
index a5a27a0..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-args "--unamed-opts=command --no-handle-error --conf-parser --no-handle-version --no-handle-help"
-
-purpose "Communicate with para_server through the paraslash control port"
-
-include(header.m4)
-define(CURRENT_PROGRAM,para_client)
-define(DEFAULT_CONFIG_FILE,~/.paraslash/client.conf)
-define(DEFAULT_HISTORY_FILE,~/.paraslash/client.history)
-<qu>
-option "hostname" i
-#~~~~~~~~~~~~~~~~~~
-"ip or host to connect"
-string typestr = "host"
-default = "localhost"
-optional
-
-option "user" u
-#~~~~~~~~~~~~~~
-"paraslash username"
-string typestr = "username"
-default = "<current user>"
-optional
-
-option "server-port" p
-#~~~~~~~~~~~~~~~~~~~~~
-"port to connect"
-int typestr = "port"
-default = "2990"
-optional
-
-option "key-file" k
-#~~~~~~~~~~~~~~~~~~
-"path to private key"
-string typestr = "filename"
-optional
-details = "
-       If not given, the following files are tried, in order:
-       $HOME/.paraslash/key.$LOGNAME, $HOME/.ssh/id_rsa. It is a fatal
-       error if the key file can not be opened, or is world-readable.
-"
-</qu>
-
-include(loglevel.m4)
-include(config_file.m4)
-include(history_file.m4)
-include(complete.m4)
diff --git a/m4/gengetopt/color.m4 b/m4/gengetopt/color.m4
deleted file mode 100644 (file)
index eb08178..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<qu>
-
-option "color" C
-#~~~~~~~~~~~~~~~
-"activate color output"
-enum typestr="when"
-values = "yes","no","auto"
-default = "auto"
-optional
-
-option "log-color" -
-#~~~~~~~~~~~~~~~~~~~
-"select a color for one type of log message"
-string typestr="color_spec"
-multiple
-optional
-details="
-       The format of \"color_spec\" is [fg [bg]] [attr].
-
-       Valid colors for \"fg\" and \"bg\" are \"normal\", \"black\",
-       \"red\", \"green\", \"yellow\", \"blue\", \"magenta\",
-       \"cyan\", and \"white\".
-
-       The \"attr\" value must be one of \"bold\", \"dim\", \"ul\",
-       \"blink\", \"reverse\".
-
-       Examples:
-
-               --log-color \"debug:green\"
-               --log-color \"info:yellow bold\"
-               --log-color \"notice:white red bold\"
-"
-
-</qu>
diff --git a/m4/gengetopt/complete.m4 b/m4/gengetopt/complete.m4
deleted file mode 100644 (file)
index 14e737c..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<qu>
-option "complete" -
-#~~~~~~~~~~~~~~~~~~
-"print possible command line completions"
-       flag off
-       details = "
-       If this flag is given, </qu>CURRENT_PROGRAM<qu> reads the environment
-       variables COMP_LINE and COMP_POINT to obtain the current command line
-       and the cursor position respectively, prints possible completions
-       to stdout and exits.
-"
-</qu>
diff --git a/m4/gengetopt/compress_filter.m4 b/m4/gengetopt/compress_filter.m4
deleted file mode 100644 (file)
index 501f46c..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Dynamically adjust the volume of an audio stream"
-
-option "blocksize" b
-#~~~~~~~~~~~~~~~~~~~
-"adjust block size"
-int typestr="number"
-default="15"
-optional
-details = "
-       Larger blocksize means fewer volume adjustments per time unit.
-"
-
-option "aggressiveness" a
-#~~~~~~~~~~~~~~~~~~~~~~~~
- "controls the maximum amount to amplify by"
-int typestr="number"
-default="4"
-optional
-
-option "inertia" i
-#~~~~~~~~~~~~~~~~~
- "how much inertia ramping has"
- int typestr="number"
-default="6"
-optional
-
-option "target-level" t
-#~~~~~~~~~~~~~~~~~~~~~~
-"target signal level (0-32768)"
-int typestr="number"
-default="20000"
-optional
-
-option "damp" d
-#~~~~~~~~~~~~~~
-"if non-zero, scale down after normalizing"
-int typestr="number"
-default="0"
-optional
diff --git a/m4/gengetopt/config_file.m4 b/m4/gengetopt/config_file.m4
deleted file mode 100644 (file)
index 29f66b4..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<qu>
-option "config-file" c
-#~~~~~~~~~~~~~~~~~~~~~
-"(default='</qu>DEFAULT_CONFIG_FILE<qu>')"
-string typestr="filename"
-optional
-details="
-       </qu>CURRENT_PROGRAM<qu> reads its config file right after parsing
-       the options that were given at the command line. If an
-       option is given both at the command line and in the
-       config file, the value that was specified at the command line
-       takes precedence.
-"
-</qu>
diff --git a/m4/gengetopt/daemon.m4 b/m4/gengetopt/daemon.m4
deleted file mode 100644 (file)
index ebead6a..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<qu>
-option "daemon" d
-#~~~~~~~~~~~~~~~~
-"run as background daemon"
-flag off
-details = "
-       If this option is given and no logfile was specified, all
-       messages go to /dev/null.
-"
-</qu>
diff --git a/m4/gengetopt/dccp_recv.m4 b/m4/gengetopt/dccp_recv.m4
deleted file mode 100644 (file)
index 1ba3fb5..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Receive a DCCP audio stream"
-
-option "host" i
-"ip or host"
-string default="localhost"
-optional
-details="
-       Both IPv4 and IPv6 addresses are supported.
-"
-
-option "port" p
-"port to connect to"
-int
-default="8000"
-optional
-
-option "ccid" c
-"CCID preference(s) for this connection"
-int
-# restrict the maximum number of times this option can be passed
-optional multiple(-10)
-# currently known CCIDs:
-# - CCID-2 (RFC 4341),
-# - CCID-3 (RFC 4342),
-# - CCID-4 (RFC 5622),
-# - CCID-248 ... CCID-254 are experimental (RFC 4340, 19.5)
-values="2", "3", "4", "248", "249", "250", "251", "252", "253", "254"
-details="
-       When present exactly once, this option mandates the CCID for the
-       sender-receiver connection. If it is passed more than once, it sets
-       a preference list where the order of appearance signifies descending
-       priority. For example, passing 4, 2, 3 creates the preference list
-       (CCID-4, CCID-2, CCID-3), assigning CCID-4 highest preference.
-
-       The request is reconciled with the CCIDs on the server through the
-       'server-priority' mechanism of RFC 4340 6.3.1/10. The server CCIDs
-       can be listed by calling 'para_client si'.
-
-"
diff --git a/m4/gengetopt/fade.m4 b/m4/gengetopt/fade.m4
deleted file mode 100644 (file)
index 59389ff..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-args "--conf-parser --no-handle-version --no-handle-help"
-
-purpose "An alarm clock and volume-fader for OSS and ALSA"
-
-include(header.m4)
-define(CURRENT_PROGRAM,para_fade)
-define(DEFAULT_CONFIG_FILE,~/.paraslash/fade.conf)
-<qu>
-section "General options"
-#########################
-
-</qu>
-include(config_file.m4)
-include(loglevel.m4)
-<qu>
-option "mode" o
-#~~~~~~~~~~~~~~
-"how to fade volume"
-       enum typestr = "mode"
-       values = "sleep", "fade", "set", "snooze"
-       default = "sleep"
-       optional
-       details="
-               para_fade knows the following modes:
-
-               sleep mode: Change to the initial volume and select
-               the initial afs mood/playlist. Then fade out until
-               the fade-out volume is reached. Switch to the
-               sleep mood/playlist until wake time minus fade-in
-               time. Finally switch to the wake mood/playlist and
-               fade to the fade-in volume.
-
-               fade: Fade the volume to the given value in the
-               given time.
-
-               set: Just set the value and exit.
-
-               snooze: Fade out, sleep a bit and fade in.
-"
-
-option "mixer-api" a
-#~~~~~~~~~~~~~~~~~~~
-"choose the mixer API"
-       string typestr = "api"
-       optional
-       details = "
-               ALSA is preferred over OSS if both APIs are supported
-               and this option is not given. To see the supported
-               mixer APIs, use this option with an invalid string
-               as the mixer API, e.g. --mixer-api help.
-       "
-
-option "mixer-device" m
-#~~~~~~~~~~~~~~~~~~~~~~
-"choose mixer device"
-       string typestr = "device"
-       optional
-       details = "
-               The default device (used if this option is not given)
-               depends on the selected mixer API. For ALSA, the
-               default is 'hw:0' which corresponds to the first sound
-               device. For OSS, '/dev/mixer' is used as the default.
-       "
-
-option "mixer-channel" C
-#~~~~~~~~~~~~~~~~~~~~~~~
-"select the analog mixer channel"
-       string typestr = "channel"
-       optional
-       details = "
-               For the ALSA mixer API, the possible values are
-               determined at runtime depending on the hardware and
-               can be printed by specifying an invalid mixer channel,
-               for example --mixer-channel help. The default channel
-               is 'Master'.
-
-               For OSS the possible values are invariably 'volume',
-               'bass', 'treble', 'synth', 'pcm', 'speaker', 'line',
-               'mic', 'cd', 'imix', 'altpcm', 'reclev', 'igain',
-               'ogain'. However, not all listed channels might be
-               supported on any particular hardware. The default
-               channel is 'volume'.
-       "
-
-section "Options for sleep mode"
-################################
-
-option "ivol" -
-#~~~~~~~~~~~~~~
-"set initial volume"
-       string typestr = "[channel:]volume"
-       default = "60"
-       optional
-       multiple
-       details = "
-               Used as the start volume, before fading out to the
-               fade out volume. The channel part may be omitted, in
-               which case the default channel is used. This option
-               may be given multiple times.
-       "
-
-option "fo-mood" -
-#~~~~~~~~~~~~~~~~~
-"afs mood/playlist during fade out"
-       string typestr = "mood_spec"
-       default = "m/fade"
-       optional
-       details = "
-               Select this mood right after setting the
-               volume. Example: --fo-mood m/sleep
-"
-
-option "fo-time" -
-#~~~~~~~~~~~~~~~~~
-"fall asleep fade out time"
-       int typestr = "seconds"
-       default = "1800"
-       optional
-       details = "
-               No fading if set to 0.
-       "
-
-option "fo-vol" -
-#~~~~~~~~~~~~~~~~
-"volume to fade out to"
-       int typestr = "volume"
-       default = "20"
-       optional
-
-option "sleep-mood" -
-#~~~~~~~~~~~~~~~~~~~~
-"sleep time afs mood/playlist"
-       string typestr = "mood_spec"
-       default = "m/sleep"
-       optional
-       details = "
-               Select the given afs mood/playlist after the fade
-               out is complete. If unset, the \"stop\" command is
-               sent to para_server.
-       "
-
-option "wake-hour" H
-#~~~~~~~~~~~~~~~~~~~
-"(0-23) (default: now + 9 hours)"
-       int typestr = "hour"
-       optional
-
-option "wake-min" M
-#~~~~~~~~~~~~~~~~~~
-"(0-59)"
-       int typestr = "minutes"
-       default = "0"
-       optional
-
-option "fi-mood" -
-#~~~~~~~~~~~~~~~~~
-"afs mood/playlist during fade in"
-       string typestr = "mood_spec"
-       default = "m/wake"
-       optional
-       details = "
-               Change to this afs mood/playlist on wake time.
-       "
-
-option "fi-time" -
-#~~~~~~~~~~~~~~~~~
-"wake up fade in time"
-       int typestr="seconds"
-       default="1200"
-       optional
-       details = "
-               No fading in if set to 0.
-       "
-
-option "fi-vol" -
-#~~~~~~~~~~~~~~~~
-"volume to fade to at wake time"
-       int typestr = "volume"
-       default = "80"
-       optional
-
-section "Options for snooze mode"
-#################################
-
-option "so-time" -
-#~~~~~~~~~~~~~~~~~
-"snooze-out time"
-       int typestr = "seconds"
-       default = "30"
-       optional
-
-option "so-vol" -
-#~~~~~~~~~~~~~~~~
-"volume to fade to before snooze"
-       int typestr = "volume"
-       default = "20"
-       optional
-
-option "snooze-time" -
-#~~~~~~~~~~~~~~~~~~~~~
-"delay"
-       int typestr = "seconds"
-       default = "600"
-       optional
-
-option "si-time" -
-#~~~~~~~~~~~~~~~~~
-"snooze-in time"
-       int typestr = "seconds"
-       default = "180"
-       optional
-
-option "si-vol" -
-#~~~~~~~~~~~~~~~~
-"volume to fade to after snooze"
-       int typestr = "volume"
-       default = "80"
-       optional
-
-section "Options for fade mode"
-###############################
-
-option "fade-vol" f
-#~~~~~~~~~~~~~~~~~~
-"volume to fade to"
-       int typestr = "volume"
-       default = "50"
-       optional
-
-option "fade-time" t
-#~~~~~~~~~~~~~~~~~~~
-"fading time"
-       int typestr = "seconds"
-       default = "5"
-       optional
-
-section "Options for set mode"
-##############################
-
-option "val" -
-"value to set"
-       int typestr = "value"
-       default = "0"
-       optional
-
-</qu>
diff --git a/m4/gengetopt/file_write.m4 b/m4/gengetopt/file_write.m4
deleted file mode 100644 (file)
index 4f98884..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Output plugin that writes to a local file"
-
-option "filename" f
-#~~~~~~~~~~~~~~~~~~
-"specify output file name"
-string typestr="filename"
-optional
-details="
-       Defaults to a random filename in ~/.paraslash.
-"
-
diff --git a/m4/gengetopt/filter.m4 b/m4/gengetopt/filter.m4
deleted file mode 100644 (file)
index b8b49f6..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-args "--no-handle-help --no-handle-version --conf-parser"
-
-purpose "Decode or process audio data from STDIN to STDOUT"
-
-include(header.m4)
-include(loglevel.m4)
-<qu>
-option "filter" f
-#~~~~~~~~~~~~~~~~
-"Specify filter."
-string typestr="filter_spec"
-optional
-multiple
-details="
-       May be given multiple times to 'pipe' the stream through
-       arbitrary many filters (without copying the data). The same
-       filter may appear more than once, order matters.
-
-       Options for a particular filter may be specified for each
-       given '--filter' option separately. You will have to quote
-       these options like this:
-
-               --filter 'compress --inertia 5 --damp 2'
-"
-</qu>
diff --git a/m4/gengetopt/group.m4 b/m4/gengetopt/group.m4
deleted file mode 100644 (file)
index 2a59ad9..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-option "group" g
-#~~~~~~~~~~~~~~~
-"set group id"
-string typestr="group"
-optional
-details="
-       This option sets the group id according to 'group'. This option
-       is silently ignored if EUID != 0. Otherwise, real/effective
-       GID and the saved set-group ID are all set to the GID given by
-       'group'. Must not be given in the config file.
-"
-
diff --git a/m4/gengetopt/gui.m4 b/m4/gengetopt/gui.m4
deleted file mode 100644 (file)
index a6b718e..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-args "--conf-parser --no-handle-version --no-handle-help"
-
-purpose "Show para_audiod status in a curses window"
-
-include(header.m4)
-define(CURRENT_PROGRAM,para_gui)
-define(DEFAULT_CONFIG_FILE,~/.paraslash/gui.conf)
-
-<qu>
-#########################
-section "General options"
-#########################
-</qu>
-
-include(config_file.m4)
-include(loglevel.m4)
-
-<qu>
-option "timeout" t
-#~~~~~~~~~~~~~~~~~
-"set timeout"
-int typestr = "milliseconds"
-default = "30"
-optional
-
-option "theme" T
-#~~~~~~~~~~~~~~~
-"select startup theme"
-string typestr = "name"
-optional
-details = "
-       If this option is not given the default theme is used.
-       If the given name is not a valid theme name, the list of
-       available themes is printed and the program terminates.
-"
-
-option "stat-cmd" s
-#~~~~~~~~~~~~~~~~~~
-"command to read status items from"
-string typestr = "command"
-default = "para_audioc -- stat -p"
-optional
-details = "
-       On a host on which the para_audiod service is not is running, the
-       default command will fail. An alternative is
-
-               para_client -- stat -p
-
-       This command connects para_server instead of para_audiod. However,
-       no timing information about the current audio file is printed.
-"
-
-#---------------------------------
-section "Mapping keys to commands"
-#---------------------------------
-
-option "key-map" k
-#~~~~~~~~~~~~~~~~~
-"Map key k to command c using mode m."
-
-string typestr = "k:m:c"
-optional
-multiple
-details = "
-       Mode may be d, x or p for display, external and paraslash
-       commands, respectively. Of course, this option may be given
-       multiple times, one for each key mapping.
-"
-</qu>
diff --git a/m4/gengetopt/header.m4 b/m4/gengetopt/header.m4
deleted file mode 100644 (file)
index c423187..0000000
+++ /dev/null
@@ -1 +0,0 @@
-changequote(<qu>,</qu>)
diff --git a/m4/gengetopt/history_file.m4 b/m4/gengetopt/history_file.m4
deleted file mode 100644 (file)
index 73e98a7..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<qu>
-option "history-file" -
-#~~~~~~~~~~~~~~~~~~~~~~
-"(default='</qu>DEFAULT_HISTORY_FILE<qu>')"
-string typestr = "filename"
-optional
-details = "
-       If </qu>CURRENT_PROGRAM<qu> runs in interactive mode, it reads the history
-       file on startup. Upon exit, the in-memory history is appended
-       to the history file.
-"
-</qu>
diff --git a/m4/gengetopt/http_recv.m4 b/m4/gengetopt/http_recv.m4
deleted file mode 100644 (file)
index 6db3ff0..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Receive an HTTP audio stream"
-
-include(header.m4)
-
-<qu>
-option "host" i
-#~~~~~~~~~~~~~~
-"ip or host"
-string
-default="localhost"
-optional
-details="
-       Both IPv4 and IPv6 addresses are supported.
-"
-
-option "port" p
-#~~~~~~~~~~~~~~
-"tcp port to connect to"
-int default="8000"
-optional
-</qu>
diff --git a/m4/gengetopt/log_timing.m4 b/m4/gengetopt/log_timing.m4
deleted file mode 100644 (file)
index ac0ea84..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<qu>
-option "log-timing" T
-#~~~~~~~~~~~~~~~~~~~~
-"show milliseconds in log messages"
-flag off
-details = "
-       Selecting this option causes milliseconds to be included in
-       the log message output. This allows to measure the interval
-       between log messages in milliseconds which is useful for
-       identifying timing problems.
-"
-</qu>
diff --git a/m4/gengetopt/logfile.m4 b/m4/gengetopt/logfile.m4
deleted file mode 100644 (file)
index 070d736..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<qu>
-option "logfile" L
-#~~~~~~~~~~~~~~~~~
-"where to write log output"
-string typestr="filename"
-optional
-details="
-       If this option is not given, </qu>CURRENT_PROGRAM<qu> writes the log
-       messages to stderr.
-"
-</qu>
diff --git a/m4/gengetopt/loglevel.m4 b/m4/gengetopt/loglevel.m4
deleted file mode 100644 (file)
index 162d030..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<qu>
-option "loglevel" l
-#~~~~~~~~~~~~~~~~~~
-"set loglevel"
-string typestr="level"
-values = "debug","info","notice","warning","error","crit","emerg"
-default="warning"
-optional
-details="
-       Log only messages with severity greater or equal the given
-       value.
-
-       debug: Produces really noisy output.
-       info: Still noisy, but won't fill up the disk quickly.
-       notice: Indicates normal, but significant event.
-       warning: Unexpected events that can be handled.
-       error: Unhandled error condition.
-       crit: System might be unreliable.
-       emerg: Last message before exit.
-"
-
-</qu>
diff --git a/m4/gengetopt/makefile b/m4/gengetopt/makefile
deleted file mode 100644 (file)
index 0db9d10..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-define ggo_opts
-       --output-dir=$(cmdline_dir) \
-       --set-version="$(GIT_VERSION)" \
-       --arg-struct-name=$(*F)_args_info \
-       --file-name=$(*F).cmdline \
-       --func-name=$(*F)_cmdline_parser \
-       --set-package="para_$(*F)"
-endef
-
-.PRECIOUS: $(cmdline_dir)/%.cmdline.c $(cmdline_dir)/%.cmdline.h $(ggo_dir)/%.ggo
-
-$(cmdline_dir)/%.cmdline.h $(cmdline_dir)/%.cmdline.c: $(ggo_dir)/%.ggo | $(cmdline_dir)
-       @[ -z "$(Q)" ] || echo 'GGO $<'
-       $(Q) $(GENGETOPT) $(ggo_opts) < $<
-ifeq ($(ggo_descriptions_declared),no)
-       @echo 'extern const char *$(*F)_args_info_description;' >> $(cmdline_dir)/$(*F).cmdline.h
-endif
-
-$(m4depdir)/%.m4d: $(m4_ggo_dir)/%.m4 | $(m4depdir)
-       @[ -z "$(Q)" ] || echo 'M4D $<'
-       $(Q) $(M4) -I $(m4_ggo_dir) -s $< \
-       | awk '{if ($$1 ~ /#line/) {gsub(/"/, "", $$3); if ($$3 != "$<") \
-       print "$(ggo_dir)/$(*F).ggo: " $$3}}' | sort | uniq > $@
-
-$(ggo_dir)/%.ggo: $(m4_ggo_dir)/%.m4 $(m4_ggo_dir)/header.m4 | $(ggo_dir)
-       @[ -z "$(Q)" ] || echo 'M4 $<'
-       $(Q) $(M4) -I $(m4_ggo_dir) $< > $@
diff --git a/m4/gengetopt/mp3dec_filter.m4 b/m4/gengetopt/mp3dec_filter.m4
deleted file mode 100644 (file)
index 8b18783..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Decode an mp3 stream"
-
-include(header.m4)
-
-<qu>
-option "ignore-crc" i
-#~~~~~~~~~~~~~~~~~~~~
-"ignore CRC information in the audio stream."
-flag off
-details="
-       This causes frames with CRC errors to be decoded and played
-       anyway. This option is not recommended, but since some encoders
-       have been known to generate bad CRC information, this option
-       is a work-around to play streams from such encoders.
-"
-</qu>
diff --git a/m4/gengetopt/oss_write.m4 b/m4/gengetopt/oss_write.m4
deleted file mode 100644 (file)
index 578d813..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Output plugin for the Open Sound System"
-
-option "device" d
-#~~~~~~~~~~~~~~~~
-"set PCM device"
-string typestr="device"
-default="/dev/dsp"
-optional
-details = ""
diff --git a/m4/gengetopt/osx_write.m4 b/m4/gengetopt/osx_write.m4
deleted file mode 100644 (file)
index 83ed737..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Output plugin for Mac OS coreaudio"
-
-section "osx options"
-#####################
-
-option "numbuffers" n
-#~~~~~~~~~~~~~~~~~~~~~
-
-"number of audio buffers to allocate (increase if
-you get buffer underruns)"
-
-       int typestr="num"
-       default="20"
-       optional
-       details = ""
diff --git a/m4/gengetopt/play.m4 b/m4/gengetopt/play.m4
deleted file mode 100644 (file)
index 1687559..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-args "--unamed-opts=audio_file --no-handle-version --conf-parser --no-handle-help"
-
-purpose "Command line audio player"
-
-description "para_play operates either in command mode or in insert
-mode. In insert mode it presents a prompt and allows to enter commands
-like stop, play, pause etc. In command mode the current audio file
-is shown and the program reads single key strokes from stdin. Keys
-may be mapped to commands. Whenever a mapped key is pressed, the
-associated command is executed."
-
-include(header.m4)
-define(CURRENT_PROGRAM,para_play)
-define(DEFAULT_CONFIG_FILE,~/.paraslash/play.conf)
-define(DEFAULT_HISTORY_FILE,~/.paraslash/play.history)
-
-<qu>
-#########################
-section "General options"
-#########################
-</qu>
-include(loglevel.m4)
-include(config_file.m4)
-include(history_file.m4)
-<qu>
-
-###############################
-section "Options for para_play"
-###############################
-
-option "randomize" z
-#~~~~~~~~~~~~~~~~~~~
-"randomize playlist at startup."
-flag off
-
-option "key-map" k
-#~~~~~~~~~~~~~~~~~
-"Map key k to a command."
-
-string typestr = "key:command [args]"
-optional
-multiple
-details = "
-       This option may be given multiple times, one for each key
-       mapping. Example:
-               5:jmp 50
-"
-</qu>
diff --git a/m4/gengetopt/prebuffer_filter.m4 b/m4/gengetopt/prebuffer_filter.m4
deleted file mode 100644 (file)
index 20ff86f..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Delay processing of an audio stream"
-
-option "duration" d
-#~~~~~~~~~~~~~~~~~~
-"prebuffer time"
-int typestr="milliseconds"
-default="200"
-optional
-details="
-       Wait that many milliseconds before letting data go through.
-       The time interval starts when the first data byte is seen by
-       this filter.
-"
-
-option "size" s
-#~~~~~~~~~~~~~~
-"amount of data to prebuffer"
-int typestr="bytes"
-default="0"
-optional
-details="
-       Wait until that many data bytes are available in the input buffer.
-       The default value of zero means to not prebuffer by size at all.
-       If both --duration and --size options are given and non-zero, the
-       filter waits until both conditions are met.
-"
diff --git a/m4/gengetopt/priority.m4 b/m4/gengetopt/priority.m4
deleted file mode 100644 (file)
index 0b37dc0..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-option "priority" -
-#~~~~~~~~~~~~~~~~~~
-"adjust scheduling priority"
-int typestr = "prio"
-default = "0"
-optional
-details = "
-       The priority (also known as nice value) is a value in the range -20
-       to 19. Lower priorities cause more favorable scheduling. Since only
-       privileged processes may request a negative priority, specifying
-       a negative value works only if the daemon is started with root
-       privileges.
-
-       Failure to set the given priority value is not considered an error
-       but a log message is printed in this case.
-"
diff --git a/m4/gengetopt/recv.m4 b/m4/gengetopt/recv.m4
deleted file mode 100644 (file)
index 9a9a880..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-args "--no-handle-help --no-handle-version"
-
-purpose "A command line HTTP/DCCP/UDP stream grabber"
-
-include(header.m4)
-include(loglevel.m4)
-
-<qu>
-option "receiver" r
-"Select receiver"
-string typestr="receiver_spec"
-default="http"
-optional
-details="
-       Any options for the selected receiver must
-       be quoted. Example:
-
-               -r 'http -i www.paraslash.org -p 8009'
-"
-</qu>
diff --git a/m4/gengetopt/resample_filter.m4 b/m4/gengetopt/resample_filter.m4
deleted file mode 100644 (file)
index 2e5f400..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Transform raw audio to a different sample rate"
-
-include(header.m4)
-
-option "converter" C
-#~~~~~~~~~~~~~~~~~~~
-"choose converter type"
-enum typestr = "type"
-values = "best", "medium", "fastest", "zero_order_hold", "linear"
-default = "medium"
-optional
-
-details = "
-       best: This is a bandlimited interpolator derived from the
-       mathematical sinc function and this is the highest quality
-       sinc based converter, providing a worst case Signal-to-Noise
-       Ratio (SNR) of 97 decibels (dB) at a bandwidth of 97%.
-
-       medium: This is another bandlimited interpolator much like the
-       previous one. It has an SNR of 97dB and a bandwidth of 90%. The
-       speed of the conversion is much faster than the previous one.
-
-       fastest: This is the fastest bandlimited interpolator and
-       has an SNR of 97dB and a bandwidth of 80%.
-
-       zero_order_hold: A Zero Order Hold converter (interpolated
-       value is equal to the last value). The quality is poor but
-       the conversion speed is blindlingly fast.
-
-       linear: A linear converter. Again the quality is poor, but
-       the conversion speed is blindingly fast.
-"
-
-include(channels.m4)
-include(sample_rate.m4)
-include(sample_format.m4)
-
-option "dest-sample-rate" d
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"sample rate to convert to"
-int typestr = "rate"
-default = "44100"
-optional
diff --git a/m4/gengetopt/sample_format.m4 b/m4/gengetopt/sample_format.m4
deleted file mode 100644 (file)
index c998f9d..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<qu>
-option "sample-format" f
-#~~~~~~~~~~~~~~~~~~~~~~~
-"specify sample format"
-# This must match the enum sample_format of para.h
-values = "S8", "U8", "S16_LE", "S16_BE", "U16_LE", "U16_BE" enum
-default = "S16_LE"
-optional
-details = "
-       It is only necessary to specify this for raw audio. See the
-       discussion of the --channels option.
-"
-</qu>
diff --git a/m4/gengetopt/sample_rate.m4 b/m4/gengetopt/sample_rate.m4
deleted file mode 100644 (file)
index a8332a4..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<qu>
-option "sample-rate" s
-#~~~~~~~~~~~~~~~~~~~~~
-"do not guess the input sample rate"
-int typestr = "num"
-default = "44100"
-optional
-details = "
-       It is only necessary to specify this for raw audio. See the
-       discussion of the --channels option.
-"
-</qu>
diff --git a/m4/gengetopt/server.m4 b/m4/gengetopt/server.m4
deleted file mode 100644 (file)
index 48e7a1f..0000000
+++ /dev/null
@@ -1,379 +0,0 @@
-args "--conf-parser --no-handle-version --no-handle-help"
-
-purpose "Manage and stream audio files"
-
-include(header.m4)
-define(CURRENT_PROGRAM,para_server)
-define(DEFAULT_CONFIG_FILE,~/.paraslash/server.conf)
-
-<qu>
-#########################
-section "General options"
-#########################
-</qu>
-
-include(loglevel.m4)
-include(log_timing.m4)
-include(color.m4)
-include(daemon.m4)
-include(user.m4)
-include(group.m4)
-include(priority.m4)
-
-<qu>
-option "port" p
-#~~~~~~~~~~~~~~
-"listening port"
-int typestr="portnumber"
-default="2990"
-optional
-details="
-       para_server listens on this tcp port for incoming connections
-       from clients such as para_client. If the default port is
-       changed, the corresponding option of para_client must be used
-       to connect to para_server.
-"
-
-#############################
-section "Configuration files"
-#############################
-</qu>
-
-include(logfile.m4)
-include(config_file.m4)
-
-<qu>
-option "user-list" -
-#~~~~~~~~~~~~~~~~~~~
-"(default='~/.paraslash/server.users')"
-
-string typestr="filename"
-optional
-
-
-##################################
-section "virtual streaming system"
-##################################
-
-
-option "autoplay" a
-#~~~~~~~~~~~~~~~~~~
-"start playing on startup"
-flag off
-
-option "autoplay-delay" -
-#~~~~~~~~~~~~~~~~~~~~~~~~
-"time to wait before streaming"
-int typestr="ms"
-default="0"
-optional
-dependon="autoplay"
-details="
-       If para_server is started with the autoplay option, this option
-       may be used to set up a delay before para_server streams its
-       first audio file. This is useful for example if para_server
-       and para_audiod are started during system startup. The delay
-       time should be choosen large enough so that para_audiod is
-       already up when para_server starts to stream. Of course, this
-       option depends on the autoplay option.
-"
-option "announce-time" A
-#~~~~~~~~~~~~~~~~~~~~~~~
-"grace time for clients"
-
-int typestr="ms"
-default="300"
-optional
-details="
-       Clients such as para_audiod connect to para_server and execute
-       the stat command to find out whether an audio stream is
-       currently available. This sets the delay betweeen announcing
-       the stream via the output of the stat command and sending
-       the first chunk of data.
-"
-
-#############################
-section "audio file selector"
-#############################
-
-option "afs-database-dir" D
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"location of the database"
-string typestr="path"
-optional
-details="
-       Where para_server should look for the osl database of the audio
-       file selector. The default is '~/.paraslash/afs_database-0.4'.
-"
-
-option "afs-socket" s
-#~~~~~~~~~~~~~~~~~~~~
-"Command socket for afs"
-string typestr="path"
-default="/var/paraslash/afs_command_socket-0.4"
-optional
-details="
-       For each server command that is handled by the audio file
-       selector, the child process of para_server connects to the
-       audio file selector via a local socket. This option specifies
-       the location of that socket in the file system.
-"
-option "afs-initial-mode" i
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-"Mood or playlist to load on startup."
-string typestr="<specifier>/<name>"
-optional
-
-details="
-       The argument of this option must be prefixed with either 'p/'
-       or 'm/' to indicate whether a playlist or a mood should be
-       loaded. Example:
-               --afs-initial-mode p/foo
-       loads the playlist named 'foo'.
-"
-
-#####################
-section "http sender"
-#####################
-
-
-option "http-port" -
-#~~~~~~~~~~~~~~~~~~~
-"tcp port for http streaming"
-int typestr="portnumber"
-default="8000"
-optional
-details="
-       The http sender of para_server listens on this port for
-       incoming connections. Clients are expected to send the usual
-       http request message such as 'GET / HTTP/'.
-"
-
-option "http-default-deny" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"make the http ACL a whitelist"
-flag off
-details="
-       The default is to use blacklists instead, i.e. connections
-       to the http sender are allowed unless the connecting host
-       matches a pattern given by a http-access option. This allows
-       to use access control the other way round: Connections are
-       denied from hosts which are not explicitly allowed by one or
-       more http-access options.
-"
-
-option "http-access" -
-#~~~~~~~~~~~~~~~~~~~~~
-"add an entry to the http ACL"
-string typestr="a.b.c.d/n"
-optional
-multiple
-details="
-       Add given host/network to access control list (whitelist if
-       http-default-deny was given, blacklist otherwise) before
-       opening the tcp port. This option can be given multiple
-       times. Example: '192.168.0.0/24' whitelists/blacklists the
-       256 hosts 192.168.0.x
-"
-
-option "http-no-autostart" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"do not open tcp port on startup"
-flag off
-details="
-       If this option is given, the http sender does not listen on
-       its tcp port. It may be instructed to open this port at a
-       later time by using the sender command.
-"
-
-option "http-max-clients" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"maximal number of connections"
-int typestr="number"
-default="-1"
-optional
-details="
-       The http sender will refuse connections if already that number
-       of clients are currently connected. A non-positive value
-       (the default) allows an unlimited number of simultaneous
-       connections.
-"
-
-#####################
-section "dccp sender"
-#####################
-
-
-option "dccp-port" -
-#~~~~~~~~~~~~~~~~~~~
-"port for dccp streaming"
-int typestr="portnumber"
-default="8000"
-optional
-details="
-       See http-port for details.
-"
-
-option "dccp-default-deny" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"make the dccp ACL a whitelist"
-flag off
-details="
-       See http-default-deny for details.
-"
-
-option "dccp-access" -
-#~~~~~~~~~~~~~~~~~~~~~
-"add an entry to the dccp ACL"
-string typestr="a.b.c.d/n"
-optional
-multiple
-details="
-       See http-access for details.
-"
-
-option "dccp-max-clients" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"maximal number of connections"
-int typestr="number"
-default="-1"
-optional
-details="
-       See http-max-clients for details.
-"
-
-option "dccp-max-slice-size" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"Upper bound for the FEC slice size"
-int typestr = "size"
-optional
-default = "0"
-details = "
-       If this value is non-positive (the default) the dccp sender
-       uses the maximum packet size (MPS) of the connection as the
-       slice size. The MPS is a network parameter and depends on
-       the path maximum transmission unit (path MTU) of an incoming
-       connection, i.e. on the largest packet size that can be
-       transmitted without causing fragmentation.
-
-       This option allows to use a value less than the MPS in order
-       to fine-tune application performance. Values greater than
-       the MPS of an incoming connection can not be set.
-"
-
-option "dccp-data-slices-per-group" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"The number of non-redundant slices per FEC group"
-int typestr = "num"
-optional
-default = "3"
-details = "
-       This determines the number of slices in each FEC group that are
-       necessary to decode the group. The given number must be smaller
-       than the value of the dccp-slices-per-group option below.
-
-       Note that the duration of a FEC group is proportional to the
-       product dccp-max-slice-size * dccp-data-slices-per-group.
-"
-
-option "dccp-slices-per-group" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"The total number of slices per FEC group"
-int typestr = "num"
-optional
-default = "4"
-details = "
-       This value must be larger than the value of the argument to
-       --dccp-data-slices-per-group. The difference of the two values is
-       the number of redundant slices, that is, the number of slices which
-       may be lost without causing interruptions in the audio stream.
-
-       Increase this value if you are on a lossy network.
-"
-
-####################
-section "udp sender"
-####################
-
-option "udp-target" -
-#~~~~~~~~~~~~~~~~~~~~
-"add udp target with optional port"
-string typestr="host[:port]"
-optional
-multiple
-details="
-       Add given host/port to the list of targets. The 'host' argument
-       can be either an IPv4/v6 address or hostname (RFC 3986 syntax).
-       The 'port' argument is an optional port number. If the 'port'
-       part is absent, the 'udp-default-port' value is used.
-
-       The following examples are possible targets:
-       '10.10.1.2:8000' (host:port); '10.10.1.2' (with default port);
-       '224.0.1.38:1500' (IPv4 multicast); 'localhost:8001' (hostname
-       with port); '[::1]:8001' (IPv6 localhost); '[badc0de::1]' (IPv6
-       host with default port); '[FF00::beef]:1500' (IPv6 multicast).
-
-       This option can be given multiple times, for multiple targets.
-"
-
-option "udp-no-autostart" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"do not start sending"
-flag off
-details="
-       If this option is given, udp streaming may be activated at
-       a later time by using the sender command.
-"
-
-option "udp-default-port" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"udp port to send to"
-int typestr="port"
-default="8000"
-optional
-
-option "udp-mcast-iface" -
-#~~~~~~~~~~~~~~~~~~~~~~~~~~
-"outgoing udp multicast interface"
-string
-optional
-
-option "udp-header-interval" H
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"duration for sending header"
-int typestr = "ms"
-default = "2000"
-optional
-details = "
-       As the udp sender has no idea about connected clients it
-       sends the audio file header periodically if necessary. This
-       option specifies the duration between subsequent headers are
-       sent. Smaller values decrease the average time clients have
-       to wait before starting playback, larger values decrease
-       network traffic.
-
-       Note that this affects only ogg/* and wma streams. Other
-       audio formats, including mp3, don't need an audio file header.
-"
-
-option "udp-ttl" t
-#~~~~~~~~~~~~~~~~~
-"set time to live value"
-int typestr="num"
-default="-1"
-optional
-details="
-       This option applies exclusively to multicast UDPv4/v6 streaming.
-
-       For the sending UDPv4 socket it sets the multicast Time-To-Live
-       value to \"num\".  Traditional TTL scope values are: 0=host,
-       1=network, 32=same site, 64=same region, 128=same continent,
-       255=unrestricted. Please note however that this scoping is not
-       a good solution: RFC 2365 e.g. presents a better alternative.
-
-       When using UDPv6 multicasting, the option sets the number of
-       multicast hops (as described in RFC 3493); a value of -1
-       allows the kernel to auto-select the hop value.
-"
-</qu>
diff --git a/m4/gengetopt/sync_filter.m4 b/m4/gengetopt/sync_filter.m4
deleted file mode 100644 (file)
index 1e6f5f8..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Synchronize playback between multiple clients."
-
-option "buddy" b
-#~~~~~~~~~~~~~~~
-"host to synchronize with"
-multiple
-string typestr = "url"
-optional
-details = "
-       This option may be given multiple times, one per buddy. Each
-       value may be given as a host, port pair in either IPv4 or
-       IPv6 form, with port being optional. If no port was specified
-       the listening port (as specified with --port, see below)
-       is used to send the synchronization packet to this buddy.
-"
-
-option "port" p
-#~~~~~~~~~~~~~~
-"UDP port for incoming synchronization packets"
-int typestr = "portnumber"
-default = "29900"
-optional
-details = "
-       The sync filter receives incoming synchronization packets on
-       this UDP port.
-"
-
-option "timeout" t
-#~~~~~~~~~~~~~~~~~
-"how long to wait for other clients"
-int typestr = "milliseconds"
-default = "2000"
-optional
-details = "
-       Once the sync filter receives its first chunk of input, a
-       synchronization period of the given number of milliseconds
-       begins. Playback is deferred until a synchronization packet
-       has been received from each defined buddy, or until the end
-       of the period. Buddies which did not send a synchronization
-       packet in time are temporarily disabled and are not waited for
-       during subsequent synchronization periods. They are re-enabled
-       automatically when another synchronization packet arrives.
-"
diff --git a/m4/gengetopt/udp_recv.m4 b/m4/gengetopt/udp_recv.m4
deleted file mode 100644 (file)
index dcdad4f..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-args "--no-version --no-help"
-
-purpose "Receive an UDP audio stream"
-
-option "host" i
-"ip or host to receive udp packets from"
-string default="224.0.1.38"
-optional
-details="
-       The default address resolves to DANTZ.MCAST.NET and activates
-       multicast.
-"
-
-option "port" p "udp port"
-int typestr="portnumber"
-default="8000"
-optional
-
-option "iface" I "receiving udp multicast interface"
-string
-optional
diff --git a/m4/gengetopt/user.m4 b/m4/gengetopt/user.m4
deleted file mode 100644 (file)
index 1bd5c59..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<qu>
-option "user" u
-#~~~~~~~~~~~~~~
-"run as the given user"
-string typestr="name"
-optional
-details="
-       </qu>CURRENT_PROGRAM<qu> does not need any special privileges.
-
-       If started as root (EUID == 0) this option must
-       be given at the command line (not in the configuration
-       file) so that </qu>CURRENT_PROGRAM<qu> can drop the root
-       privileges right after parsing the command line options,
-       but before parsing the configuration file. In this case,
-       real/effective/saved UID are all set to the UID of 'name'. As
-       the configuration file is read afterwards, those options that
-       have a default value depending on the UID (e.g. the directory
-       for the configuration file) are computed by using the uid of
-       'name'. This option has no effect if </qu>CURRENT_PROGRAM<qu>
-       is started as a non-root user (i.e.  EUID != 0).
-" </qu>
diff --git a/m4/gengetopt/write.m4 b/m4/gengetopt/write.m4
deleted file mode 100644 (file)
index 6667cb8..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-args "--no-handle-help --no-handle-version"
-
-purpose "Play wav or raw audio"
-
-include(header.m4)
-include(loglevel.m4)
-
-option "writer" w
-#~~~~~~~~~~~~~~~~
-"select stream writer"
-string typestr="name"
-optional
-multiple
-details="
-       May be given multiple times, and the same writer may be specified more
-       than once. If this option is not given, the first supported writer
-       is started. The list of supported writers is shown in the help output.
-"
-
-include(channels.m4)
-include(sample_rate.m4)
-include(sample_format.m4)
diff --git a/m4/lls/afh.suite.m4 b/m4/lls/afh.suite.m4
new file mode 100644 (file)
index 0000000..7ebf208
--- /dev/null
@@ -0,0 +1,100 @@
+m4_define(PROGRAM, para_afh)
+[suite afh]
+version-string = GIT_VERSION()
+[supercommand para_afh]
+       purpose = print or modify meta information about audio file(s)
+       [description]
+               By default para_afh prints information about the given audio files,
+               such as format, duration, bitrate, etc., and the content of the most
+               common meta tags (artist, title, album, year, comment). In modify
+               mode it changes the meta tags to the values given at the command line.
+       [/description]
+       non-opts-name = file...
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       [option modify-section]
+               summary = Printing meta information
+               flag ignored
+       [option chunk-table]
+               short_opt = c
+               summary = print also the chunk table
+               [help]
+                       The chunk table of an audio file is an array of offsets where each
+                       offset corresponds to chunk of encoded data. The exact meaning of
+                       chunk depends on the audio format.
+
+                       Programs which are unaware of the particular audio format can read the
+                       chunk table to obtain the timing information needed to stream the file.
+               [/help]
+       [option parser-friendly]
+               short_opt = p
+               summary = do not use human-readable output format
+               [help]
+                       This option only affects the format of the chunk table, so it has no
+                       effect if --chunk-table is not given.
+
+                       The human-readable output (the default) consists of one output line
+                       per chunk and the output contains also the chunk number, the duration
+                       and the size of each chunk. The parser-friendly output prints only
+                       the offsets, in one line.
+               [/help]
+       [option modify-section]
+               summary = Modifying meta tags
+               flag ignored
+       [option modify]
+               short_opt = m
+               summary = modify (rather than print) tags
+               [help]
+                       If this option is given, para_afh creates a temporary copy of the given
+                       file(s), with meta tags changed according to the options below. On
+                       errors, the temporary file is removed, leaving the original file
+                       unchanged. On success, if --backup is given, the original file is
+                       moved away. Finally the temporary file is renamed to the name of the
+                       original file.
+               [/help]
+       [option backup]
+               short_opt = b
+               summary = create backup of the original file
+               [help]
+                       The backup suffix is '~'. That is, a single tilde character is appended
+                       to the given file name.
+               [/help]
+       [option preserve]
+               summary = preserve modification time
+               [help]
+                       If this option is given, the mtime of the modified file is set to
+                       the value prior to the modification.
+               [/help]
+       [option year]
+               short_opt = y
+               summary = set the year tag
+               arg_info = required_arg
+               arg_type = string
+               typestr = string
+       [option title]
+               short_opt = t
+               summary = set the title tag
+               arg_info = required_arg
+               arg_type = string
+               typestr = string
+       [option artist]
+               short_opt = a
+               summary = set the artist tag
+               arg_info = required_arg
+               arg_type = string
+               typestr = string
+       [option album]
+               short_opt = A
+               summary = set the album tag
+               arg_info = required_arg
+               arg_type = string
+               typestr = string
+       [option comment]
+               short_opt = C
+               summary = set the comment tag
+               arg_info = required_arg
+               arg_type = string
+               typestr = string
diff --git a/m4/lls/audioc.suite.m4 b/m4/lls/audioc.suite.m4
new file mode 100644 (file)
index 0000000..6ad8c71
--- /dev/null
@@ -0,0 +1,39 @@
+m4_define(PROGRAM, para_audioc)
+m4_define(DEFAULT_HISTORY_FILE, ~/.paraslash/audioc.history)
+[suite audioc]
+version-string = GIT_VERSION()
+[supercommand para_audioc]
+       purpose = communicate with para_audiod through a local socket
+       non-opts-name = [command [options]]
+       [description]
+               The client program to control para_audiod at runtime. It can
+               enable/disable streaming, receive status info, or grab the audio
+               stream at any point of the decoding process.
+
+               If no command is given, para_audioc enters interactive mode.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       m4_include(complete.m4)
+       m4_include(history-file.m4)
+       m4_include(per-command-options-section.m4)
+       [option socket]
+               short_opt = s
+               summary = path to well-known socket
+               arg_info = required_arg
+               arg_type = string
+               typestr = path
+               [help]
+                       The default path of the socket is
+                       /var/paraslash/audiod.socket.$HOSTNAME.
+               [/help]
+       [option bufsize]
+               short_opt = b
+               summary = size of internal buffer
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = bytes
+               default_val = 8192
diff --git a/m4/lls/audiod.suite.m4 b/m4/lls/audiod.suite.m4
new file mode 100644 (file)
index 0000000..31f6042
--- /dev/null
@@ -0,0 +1,191 @@
+m4_define(PROGRAM, para_audiod)
+m4_define(DEFAULT_CONFIG_FILE, ~/.paraslash/audiod.conf)
+[suite audiod]
+version-string = GIT_VERSION()
+[supercommand para_audiod]
+       purpose = connect to para_server, receive, decode and play audio streams
+       [description]
+               para_audiod runs on a host with an audio device and connects
+               para_server to obtain status information and receive the current audio
+               stream. The stream is transformed through any number of filters and
+               then written to the configured output device.
+
+               Moreover, para_audiod listens on a local socket and sends status
+               information to local clients on request. Access to the local socket
+               can be restricted by means of Unix socket credentials, if available.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(config-file.m4)
+       m4_include(loglevel.m4)
+       m4_include(logfile.m4)
+       m4_include(color.m4)
+       m4_include(log-timing.m4)
+       m4_include(daemon.m4)
+       m4_include(user.m4)
+       m4_include(group.m4)
+       m4_include(priority.m4)
+       m4_include(per-command-options-section.m4)
+       [option force]
+               summary = force startup
+               short_opt = F
+               [help]
+                       If this flag is not given, para_audiod refuses to start if the
+                       well-known socket file (see the --socket option) already exists
+                       because this usually means that para_audiod is already running and
+                       listening on that socket. After a crash or if para_audiod received
+                       a SIGKILL signal, a stale socket file might remain and you have to
+                       use --force once to force startup of para_audiod.
+               [/help]
+       [option mode]
+               summary = select startup mode
+               arg_info = required_arg
+               arg_type = string
+               typestr = mode
+               values = {AUDIOD_ON = "on", AUDIOD_OFF = "off",  AUDIOD_STANDBY = "sb"}
+               default_val = on
+               [help]
+                       para_audiod supports three modes of operation: On, off and standby
+                       (sb).  This option selects the mode that should be used at startup. If
+                       mode is "on", para_audiiod connects para_server to receive status
+                       information. If the server announces the availability of an audio
+                       stream, para_audiod downloads, decodes and plays the audio stream
+                       according to the given stream I/O options, see below.
+
+                       In "standby" mode, para_audiod only receives the status information
+                       from para_server but does not download the audio stream.
+
+                       In "off" mode, para_audiod does not connect para_server at all,
+                       but still listens on the local socket.
+               [/help]
+       [option socket]
+               short_opt = s
+               summary = path to the well-known socket
+               arg_info = required_arg
+               arg_type = string
+               typestr = path
+               [help]
+                       para_audiod listens on a "well-known" socket for connections
+                       from para_audioc. This socket is a special file in the file system;
+                       its location defaults to /var/paraslash/audiod_sock.$hostname.
+
+                       para_audioc, the client program used to connect to para_audiod,
+                       opens this socket in order to talk to para_audiod. If the default
+                       value for para_audiod is changed, para_audioc must be instructed to
+                       use also <path> to connect para_audiod.
+               [/help]
+       [option user-allow]
+               summary = allow this user to connect to audiod
+               arg_info = required_arg
+               arg_type = string
+               typestr = username
+               [help]
+                       Allow the user identified by username (either a string or a UID) to
+                       connect to para_audiod. This option may be given multiple times. If
+                       not specified at all, all users are allowed to connect.
+
+                       This feature is based on the ability to send unix credentials through
+                       local sockets using ancillary data (SCM_CREDENTIALS). Currently it
+                       only works on Linux. On other operating systems the option is silently
+                       ignored and all local users are allowed to connect.
+               [/help]
+       [option clock-diff-count]
+               summary = sync clock on startup
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = count
+               [help]
+                       Check the clock difference between the host running para_server and
+                       the local host running para_audiod this many times before starting
+                       any stream I/0. Set this to non-zero for non-local setups if the
+                       clocks of these two hosts are not synchronized by ntp or similar.
+               [/help]
+       [option Stream-IO-options]
+               summary = Stream I/O options
+               flag ignored
+       [option receiver]
+               short_opt = r
+               summary = select receiver
+               arg_info = required_arg
+               arg_type = string
+               typestr = receiver_spec
+               default_val = http
+               flag multiple
+               [help]
+                       This option may be given multiple times, for each audio format
+                       separately. If multiple definitions for an audio format are given,
+                       the first one is selected.
+
+                       The <receiver_spec> consists of an audio format specifier and one or
+                       more receiver arguments, separated by a colon.
+
+                       The audio format specifier is a regular expression which specifies
+                       the set of audio formats for which this option should apply.
+
+                       If any receiver options are present, the whole receiver argument must
+                       be quoted:
+
+                               -r 'mp3:http -i my.host.org -p 8009'
+
+                       Since a single dot '.' matches the name of any audio format, specifying
+                       '.' instead of 'mp3' above activates the http receiver for all audio
+                       formats.
+               [/help]
+       [option filter]
+               short_opt = f
+               summary = specify the filter configuration
+               arg_info = required_arg
+               arg_type = string
+               typestr = filter_spec
+               flag multiple
+               [help]
+                       This option may be given multiple times. The <filter_spec< consists
+                       of an audio format specifier (see above), the name of the filter,
+                       and any options for that filter. Note that order matters.
+
+                       The compiled-in defaults apply to all audio formats for which no
+                       --filter option was given. These defaults depend on the receiver
+                       being used.
+
+                       For HTTP streams, only the decoder for the current audio format
+                       is activated. UDP and DCCP streams, on the other hand, are sent
+                       FEC-encoded by para_server. In order to play such streams, the
+                       receiver output must be FEC-decoded first, i.e. fed to the fecdec
+                       filter. Therefore the default for UDP and DCCP streams is to activate
+                       the fecdec filter, followed by the decoding filter for the audio
+                       format.
+
+                       Examples:
+
+                               --filter 'mp3:mp3dec'
+
+                               --filter 'mp3|aac:compress --inertia 5 --damp 2'
+
+                               --filter '.:fecdec'
+               [/help]
+       [option writer]
+               short_opt = w
+               summary = specify one or more stream writers
+               arg_info = required_arg
+               arg_type = string
+               typestr = writer_spec
+               flag multiple
+               [help]
+                       May be given multiple times, even multiple times for the same audio
+                       format. The default is to use the first supported writer. Example:
+
+                               --writer 'aac|wma:oss'
+               [/help]
+       [option stream-delay]
+               summary = specify time interval for client sync
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = count
+               [help]
+                       Check the clock difference between the host running para_server and
+                       the local host running para_audiod this many times before starting
+                       any stream I/0. Set this to non-zero for non-local setups if the
+                       clocks of these two hosts are not synchronized by ntp or similar.
+               [/help]
diff --git a/m4/lls/audiod_cmd.suite.m4 b/m4/lls/audiod_cmd.suite.m4
new file mode 100644 (file)
index 0000000..80ae2e4
--- /dev/null
@@ -0,0 +1,100 @@
+[suite audiod_cmd]
+caption = list of audiod commands
+[subcommand cycle]
+       purpose = switch to next operating mode
+       [description]
+               Cycle through the three operation modes (on, standby, off).
+       [/description]
+
+[subcommand help]
+       purpose = list available commands or print command-specific help
+       non-opts-name = [subcommand]
+       [description]
+               When executed without any arguments, the available audiod commands
+               are listed. Otherwise, if the first argument is the name of an audiod
+               command, the description of this command is shown.
+       [/description]
+       m4_include(`long-help.m4')
+
+[subcommand grab]
+       purpose = grab the audio stream
+       [description]
+               grab ('splice') the audio stream at any position in the buffer tree
+               and send that data back to the client.
+       [/description]
+       [option mode]
+               short_opt = m
+               summary = change grab mode
+               arg_info = required_arg
+               arg_type = string
+               typestr = mode
+               default_val = s
+               [help]
+                       The various grab modes only differ in what happens if an attempt to
+                       write the grabbed audio data would block. Sloppy mode ("s") ignores
+                       the write, pedantic mode ("p") aborts and aggressive mode ("a")
+                       tries to write anyway.
+               [/help]
+       [option parent]
+               short_opt = p
+               summary = Grab output of the given node of the buffer tree
+               arg_info = required_arg
+               arg_type = string
+               typestr = node
+       [option name]
+               short_opt = n
+               summary = Name of the new buffer tree node. Defaults to 'grab'
+               arg_info = optional_arg
+               arg_type = string
+               typestr = string
+       [option one-shot]
+               short_opt = o
+               summary = One-shot mode: Stop grabbing if audio file changes
+
+[subcommand off]
+       purpose = deactivate para_audiod
+       [description]
+               Close connection to para_server and stop all decoders.
+       [/description]
+
+[subcommand on]
+       purpose = activate para_audiod
+       [description]
+               Establish connection to para_server, retrieve para_server's current
+               status. If playing, start corresponding decoder. Otherwise stop
+               all decoders.
+       [/description]
+
+[subcommand sb]
+       purpose = enter standby mode
+       [description]
+               Stop all decoders but leave connection to para_server open.
+       [/description]
+
+[subcommand stat]
+       purpose = print status information
+       non-opts-name = [item...]
+       [description]
+               Dump given status items (all if none given) to stdout.
+       [/description]
+       [option parser-friendly]
+               short_opt = p
+               summary = use parser-friendly output format
+
+[subcommand version]
+       purpose = print the version of para_audiod
+       [option verbose]
+               short_opt = v
+               summary = print detailed (multi-line) version text
+
+[subcommand tasks]
+       purpose = list current tasks
+       [description]
+               Print the list of task ids together with the status of each task.
+       [/description]
+
+[subcommand term]
+       purpose = terminate para_audiod
+       [description]
+               Stop all decoders, shut down connection to para_server and exit.
+       [/description]
diff --git a/m4/lls/client.suite.m4 b/m4/lls/client.suite.m4
new file mode 100644 (file)
index 0000000..30230c4
--- /dev/null
@@ -0,0 +1,63 @@
+m4_define(PROGRAM, para_client)
+m4_define(DEFAULT_HISTORY_FILE, ~/.paraslash/client.history)
+m4_define(DEFAULT_CONFIG_FILE,~/.paraslash/client.conf)
+[suite client]
+version-string = GIT_VERSION()
+[supercommand para_client]
+       purpose = Communicate with para_server through the paraslash control port
+       non-opts-name = [command [options]]
+       [description]
+               para_client is the program used to connect to a running instance of
+               para_server. If a command is provided, this command is sent to the
+               server and any output received from the server is echoed to stdout. If
+               no command is given, para_client enters interactive mode.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       m4_include(config-file.m4)
+       m4_include(history-file.m4)
+       m4_include(per-command-options-section.m4)
+       [option hostname]
+               short_opt = i
+               summary = IP address or name of the host where para_server is running
+               typestr = host
+               arg_info = required_arg
+               arg_type = string
+               default_val = localhost
+               [help]
+                       Both IPv4 and IPv6 addresses are supported.
+               [/help]
+       [option user]
+               short_opt = u
+               summary = the paraslash username to authenticate as
+               arg_info = required_arg
+               arg_type = string
+               typestr = name
+               [help]
+                       If this is not given, the current username is assumed.
+               [/help]
+       [option server-port]
+               short_opt = p
+               summary = the port on the remote host to connect
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = portnumber
+               default_val = 2990
+               [help]
+                       If this is not given, the current username is assumed.
+               [/help]
+       [option key-file]
+               short_opt = k
+               summary = path to private key
+               arg_info = required_arg
+               arg_type = string
+               typestr = filename
+               [help]
+                       If not given, the following files are tried, in order:
+                       $HOME/.paraslash/key.$LOGNAME, $HOME/.ssh/id_rsa. It is a fatal error
+                       if the key file can not be opened, or is world-readable.
+               [/help]
+       m4_include(complete.m4)
diff --git a/m4/lls/copyright.m4 b/m4/lls/copyright.m4
new file mode 100644 (file)
index 0000000..9e5c541
--- /dev/null
@@ -0,0 +1,15 @@
+.SH COPYRIGHT
+Written by Andre Noll
+.br
+Copyright (C) COPYRIGHT_YEAR() Andre Noll
+.br
+License: GNU GPL version 2
+.br
+This is free software: you are free to change and redistribute it.
+.br
+There is NO WARRANTY, to the extent permitted by law.
+.br
+Report bugs to
+.MT <maan@tuebingen.mpg.de>
+Andre Noll
+.ME
diff --git a/m4/lls/filter.suite.m4 b/m4/lls/filter.suite.m4
new file mode 100644 (file)
index 0000000..376e875
--- /dev/null
@@ -0,0 +1,49 @@
+m4_define(PROGRAM, para_filter)
+[suite filter]
+version-string = GIT_VERSION()
+[supercommand para_filter]
+       purpose = decode or process audio data from STDIN to STDOUT
+       [description]
+               This program transforms the audio stream read from STDIN by chaining
+               one or more filters. A common mode of operation is to decode an
+               mp3 file with the mp3dec filter, but many other filters exist which
+               transform the audio stream in different ways.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       m4_include(per-command-options-section.m4)
+       [option filter]
+               short_opt = f
+               summary = add one filter to the filter chain
+               arg_info = required_arg
+               arg_type = string
+               flag multiple
+               typestr = filter_spec
+               [help]
+                       A filter specifier begins with the name of a supported filter,
+                       optionally followed by zero or more options for the named filter.
+                       Filter name and options must be separated by whitespace. If the
+                       there is at least one option, the filter specifier needs to be quoted
+                       like this:
+
+                               --filter 'compress --inertia 5 --damp 2'
+
+                       This option may be specified multiple times to 'pipe' the stream
+                       through all given filters (in a single thread without copying the
+                       data). The same filter may appear more than once, and order matters.
+               [/help]
+[section Examples]
+       .IP \(bu 4
+       Decode a wma file to wav format:
+       .EX
+       \       para_filter -f wmadec -f wav < file.wma > file.wav
+       .EE
+       .IP \(bu 4
+       Amplify a raw audio file by a factor of 1.5:
+       .EX
+       \       para_filter -f amp --amp 32 < foo.raw > bar.raw
+       .EE
+[/section]
diff --git a/m4/lls/filter_cmd.suite.m4 b/m4/lls/filter_cmd.suite.m4
new file mode 100644 (file)
index 0000000..c026a62
--- /dev/null
@@ -0,0 +1,225 @@
+[suite filter_cmd]
+caption = filters
+[subcommand aacdec]
+       purpose = decode an aac stream
+[subcommand amp]
+       purpose = amplify (scale) a raw audio stream
+       [option amp]
+               short_opt = a
+               summary = amplification value
+               typestr = number
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 32
+               [help]
+                       The amplification value determines the scaling factor by which the
+                       amplitude of the audio stream is multiplied. The formula for the
+                       scaling factor is
+
+                               factor = 1 + amp / 64.
+
+                       For example, an amplification value of zero results in a scaling factor
+                       of one while an amplification value of 64 means to double the volume.
+               [/help]
+[subcommand compress]
+       purpose = dynamically adjust the volume of an audio stream
+       [option blocksize]
+               short_opt = b
+               summary = adjust volume after each block of size 2**bits (1-31)
+               typestr = bits
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 15
+               [help]
+                       Larger blocksize means fewer volume adjustments per time unit.
+               [/help]
+       [option aggressiveness]
+               short_opt = a
+               summary = controls the maximum amount to amplify by (0-10)
+               typestr = bits
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 4
+               [help]
+                       This controls the maximal gain factor. Zero means to not amplify
+                       at all while the value 10 corresponds to maximal gain factor which
+                       results in a 4-fold increase in volume.
+               [/help]
+       [option inertia]
+               short_opt = i
+               summary = how much inertia ramping has (1-14)
+               typestr = bits
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 6
+               [help]
+                       Larger values cause smaller volume adjustments.
+               [/help]
+       [option target-level]
+               short_opt = t
+               summary = target signal level (0-32767)
+               typestr = level
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 16384
+               [help]
+                       If the peak of the previous block is less than the target level,
+                       volume is increased slightly for the next block. Otherwise it is
+                       decreased. The default value is chosen to minimize clipping. There
+                       is usually no reason to change it.
+               [/help]
+       [option damp]
+               short_opt = d
+               summary = if non-zero, scale down after normalizing (0-16)
+               typestr = bits
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 0
+               [help]
+                       This scales down the volume of the audio stream by factor 2**bits.
+                       This is mostly useful if another audio application (e.g., a video
+                       game) is running in parallel and the relative volume of the audio
+                       stream is too high.
+               [/help]
+[subcommand fecdec]
+       purpose = decode a (lossy) input stream using forward error correction
+[subcommand flacdec]
+       purpose = decode a flac stream
+[subcommand mp3dec]
+       purpose = decode an mp3 stream
+       [option ignore-crc]
+               short_opt = i
+               summary = ignore CRC information in the audio stream
+               [help]
+                       This causes frames with CRC errors to be decoded and played
+                       anyway. This option is not recommended, but since some encoders
+                       have been known to generate bad CRC information, this option is a
+                       work-around to play streams from such encoders.
+               [/help]
+[subcommand oggdec]
+       purpose = decode an ogg/vorbis stream
+[subcommand opusdec]
+       purpose = decode an ogg/opus stream
+[subcommand prebuffer]
+       purpose = delay processing of an audio stream
+       [option duration]
+               short_opt = d
+               summary = length of the prebuffer period
+               typestr = milliseconds
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 200
+               [help]
+                       Wait this many milliseconds before letting data go through. The time
+                       interval starts when the first data byte is seen in the input queue.
+               [/help]
+       [option size]
+               short_opt = s
+               summary = amount of data to prebuffer
+               typestr = bytes
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 0
+               [help]
+                       Wait until this many data bytes are available in the input queue. The
+                       default value of zero means to not prebuffer by size. If both
+                       --duration and --size are given and non-zero, the prebuffer filter
+                       waits until both conditions are met.
+               [/help]
+[subcommand resample]
+       purpose = transform raw audio to a different sample rate
+       [option converter]
+               short_opt = C
+               summary = set conversion algorithm
+               typestr = type
+               arg_info = required_arg
+               arg_type = string
+               values = {
+                       # RCT: resample conversion type
+                       RCT_BEST = "best",
+                       RCT_MEDIUM = "medium",
+                       RCT_FASTEST = "fastest",
+                       RCT_ZERO_ORDER_HOLD = "zero_order_hold",
+                       RCT_LINEAR = "linear"
+               }
+               default_val = medium
+               [help]
+                       best: This is a bandlimited interpolator derived from the mathematical
+                       sinc function and this is the highest quality sinc based converter,
+                       providing a worst case Signal-to-Noise Ratio (SNR) of 97 decibels
+                       (dB) at a bandwidth of 97%.
+
+                       medium: This is another bandlimited interpolator much like the previous
+                       one. It has an SNR of 97dB and a bandwidth of 90%. The speed of the
+                       conversion is much faster than the previous one.
+
+                       fastest: This is the fastest bandlimited interpolator and has an SNR
+                       of 97dB and a bandwidth of 80%.
+
+                       zero_order_hold: A Zero Order Hold converter (interpolated value
+                       is equal to the last value). The quality is poor but the conversion
+                       speed is blindlingly fast.
+
+                       linear: A linear converter. Again the quality is poor, but the
+                       conversion speed is blindingly fast.
+               [/help]
+       [option dest-sample-rate]
+               short_opt = d
+               summary = sample rate to convert to
+               typestr = rate
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 44100
+       m4_include(channels.m4)
+       m4_include(sample-rate.m4)
+       m4_include(sample-format.m4)
+[subcommand spxdec]
+       purpose = decode an ogg/speex stream
+[subcommand sync]
+       purpose = synchronize playback between multiple clients
+       [option buddy]
+               short_opt = b
+               summary = client to synchronize with
+               typestr = url
+               arg_info = required_arg
+               arg_type = string
+               flag multiple
+               [help]
+                       This option may be given multiple times, one per buddy. Each value
+                       may be given as a host, port pair in either IPv4 or IPv6 form, with
+                       port being optional. If no port was specified the listening port (as
+                       specified with --port, see below) is used to send the synchronization
+                       packet to this buddy.
+               [/help]
+       [option port]
+               short_opt = p
+               summary = UDP port for incoming synchronization packets
+               typestr = portnumber
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 29900
+               [help]
+                       The sync filter expects incoming synchronization packets on this
+                       UDP port.
+               [/help]
+       [option timeout]
+               short_opt = t
+               summary = how long to wait for other clients
+               typestr = milliseconds
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 2000
+               [help]
+                       Once the sync filter receives its first chunk of input,
+                       a synchronization period of the given number of milliseconds
+                       begins. Playback is deferred until a synchronization packet has
+                       been received from each defined buddy, or until the end of the
+                       period. Buddies which did not send a synchronization packet in time
+                       are temporarily disabled and are not waited for during subsequent
+                       synchronization periods. They are re-enabled automatically when
+                       another synchronization packet arrives.
+               [/help]
+[subcommand wav]
+       purpose = insert a Microsoft wave header into a raw audio stream
+[subcommand wmadec]
+       purpose = decode a wma stream
diff --git a/m4/lls/gui.suite.m4 b/m4/lls/gui.suite.m4
new file mode 100644 (file)
index 0000000..e0e45ef
--- /dev/null
@@ -0,0 +1,58 @@
+m4_define(PROGRAM, para_gui)
+m4_define(DEFAULT_CONFIG_FILE, ~/.paraslash/gui.conf)
+[suite gui]
+version-string = GIT_VERSION()
+[supercommand para_gui]
+       purpose = show para_audiod status in a curses window
+       [description]
+               para_gui is a curses program which displays status information
+               about para_server and para_audiod in a terminal. Keys may be
+               mapped to user-defined commands which are executed when the key is
+               pressed. Command output is shown in a simple pager.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(config-file.m4)
+       m4_include(loglevel.m4)
+       m4_include(per-command-options-section.m4)
+       [option theme]
+               short_opt = T
+               summary = select startup theme
+               arg_info = required_arg
+               arg_type = string
+               typestr = name
+               [help]
+                       If this option is not given the default theme is used. If the given
+                       name is not a valid theme name, the list of available themes is
+                       printed and the program terminates.
+               [/help]
+       [option stat-cmd]
+               short_opt = s
+               summary = command to read status items from
+               arg_info = required_arg
+               arg_type = string
+               typestr = command
+               default_val = para_audioc -- stat -p
+               [help]
+                       On a host on which the para_audiod service is not is running, the
+                       default command will fail. An alternative is
+
+                               para_client -- stat -p
+
+                       This command connects para_server instead of para_audiod. However,
+                       no timing information about the current audio file is printed.
+               [/help]
+       [option key-map]
+               short_opt = k
+               summary = map a key to a command using one of three possible modes
+               arg_info = required_arg
+               arg_type = string
+               typestr = k:m:c
+               flag multiple
+               [help]
+                       Mode may be d, x or p for display, external and paraslash commands,
+                       respectively. Of course, this option may be given multiple times,
+                       one for each key mapping. See the manual for more information.
+               [/help]
diff --git a/m4/lls/include/channels.m4 b/m4/lls/include/channels.m4
new file mode 100644 (file)
index 0000000..ae26b73
--- /dev/null
@@ -0,0 +1,13 @@
+[option channels]
+       short_opt = c
+       summary = specify number of channels
+       typestr = num
+       arg_info = required_arg
+       arg_type = uint32
+       default_val = 2
+       [help]
+               It is only necessary to specify this option for raw audio. If it is
+               not given, the channel count is queried from the parent buffer tree
+               nodes (e.g. the decoder) or the wav header. Only if this query fails,
+               the default value applies.
+       [/help]
diff --git a/m4/lls/include/color.m4 b/m4/lls/include/color.m4
new file mode 100644 (file)
index 0000000..a23b325
--- /dev/null
@@ -0,0 +1,29 @@
+[option color]
+       short_opt = C
+       summary = activate color output
+       typestr = when
+       arg_info = required_arg
+       arg_type = string
+       values = {COLOR_YES = "yes", COLOR_NO = "no", COLOR_AUTO = "auto"}
+       default_val = auto
+[option log-color]
+       summary = select a color for one type of log message
+       typestr = color_spec
+       arg_info = required_arg
+       arg_type = string
+       flag multiple
+       [help]
+               The format of <color_spec> is [<fg> [<bg>]] [<attr>].
+
+               Valid colors for <fg> and <bg> are "normal", "black", "red", "green",
+               "yellow", "blue", "magenta", "cyan", and "white".
+
+               The <attr> value must be one of "bold", "dim", "ul", "blink",
+               "reverse".
+
+       Examples:
+
+               --log-color "debug:green"
+               --log-color "info:yellow bold"
+               --log-color "notice:white red bold"
+       [/help]
diff --git a/m4/lls/include/common-option-section.m4 b/m4/lls/include/common-option-section.m4
new file mode 100644 (file)
index 0000000..2a3b3e6
--- /dev/null
@@ -0,0 +1,8 @@
+[option common-option-section]
+       summary = Common options
+       flag ignored
+       [help]
+               The following options are implemented generically and are available
+               for many of the commands.
+       [/help]
+
diff --git a/m4/lls/include/complete.m4 b/m4/lls/include/complete.m4
new file mode 100644 (file)
index 0000000..c9f135c
--- /dev/null
@@ -0,0 +1,8 @@
+[option complete]
+       summary = print possible command line completions
+       [help]
+               If this flag is given, the environment variables COMP_LINE and
+               COMP_POINT are examined to obtain the current command line and the
+               cursor position respectively. Possible completions are written to
+               stdout and the program exits.
+       [/help]
diff --git a/m4/lls/include/config-file.m4 b/m4/lls/include/config-file.m4
new file mode 100644 (file)
index 0000000..a5de133
--- /dev/null
@@ -0,0 +1,14 @@
+[option config-file]
+       short_opt = c
+       summary = path to alternative config file
+       arg_info = required_arg
+       arg_type = string
+       typestr = filename
+       [help]
+               default: DEFAULT_CONFIG_FILE()
+
+               PROGRAM() reads its config file right after parsing the options that
+               were given at the command line. If an option is given both at the
+               command line and in the config file, the value that was specified at
+               the command line takes precedence.
+       [/help]
diff --git a/m4/lls/include/daemon.m4 b/m4/lls/include/daemon.m4
new file mode 100644 (file)
index 0000000..5c04712
--- /dev/null
@@ -0,0 +1,7 @@
+[option daemon]
+       short_opt = d
+       summary = run as background daemon
+       [help]
+               If this option is given and no logfile was specified, all messages
+               go to /dev/null.
+       [/help]
diff --git a/m4/lls/include/detailed-help.m4 b/m4/lls/include/detailed-help.m4
new file mode 100644 (file)
index 0000000..33a10f8
--- /dev/null
@@ -0,0 +1,3 @@
+
+[option detailed-help]
+       summary = print help, including all details, and exit
diff --git a/m4/lls/include/group.m4 b/m4/lls/include/group.m4
new file mode 100644 (file)
index 0000000..8d49cc7
--- /dev/null
@@ -0,0 +1,12 @@
+[option group]
+       short_opt = g
+       summary = set group id
+       arg_info = required_arg
+       arg_type = string
+       typestr = groupname
+       [help]
+               This option sets the group id according to <group>. This option is
+               silently ignored if EUID != 0. Otherwise, real/effective GID and the
+               saved set-group ID are all set to the GID given by <group>. Must not
+               be given in the config file.
+       [/help]
diff --git a/m4/lls/include/help.m4 b/m4/lls/include/help.m4
new file mode 100644 (file)
index 0000000..ff6ce3e
--- /dev/null
@@ -0,0 +1,3 @@
+[option help]
+       summary = print help and exit
+       short_opt = h
diff --git a/m4/lls/include/history-file.m4 b/m4/lls/include/history-file.m4
new file mode 100644 (file)
index 0000000..deb3d7e
--- /dev/null
@@ -0,0 +1,13 @@
+[option history-file]
+arg_info = required_arg
+arg_type = string
+typestr = filename
+summary = location of the file for the command history list
+[help]
+       If PROGRAM() runs in interactive mode, it reads the history file
+       on startup. Upon exit, the in-memory history is appended to the
+       history file.
+
+       If this option is not given, the history file is expected at
+       DEFAULT_HISTORY_FILE().
+[/help]
diff --git a/m4/lls/include/host.m4 b/m4/lls/include/host.m4
new file mode 100644 (file)
index 0000000..3c0f0eb
--- /dev/null
@@ -0,0 +1,10 @@
+[option host]
+       short_opt = i
+       summary = IP address or hostname
+       typestr = host
+       arg_info = required_arg
+       arg_type = string
+       default_val = localhost
+       [help]
+               Both IPv4 and IPv6 addresses are supported.
+       [/help]
diff --git a/m4/lls/include/log-timing.m4 b/m4/lls/include/log-timing.m4
new file mode 100644 (file)
index 0000000..ba19be9
--- /dev/null
@@ -0,0 +1,9 @@
+[option log-timing]
+       short_opt = T
+       summary = show milliseconds in log messages
+       [help]
+               Selecting this option causes milliseconds to be included in
+               the log message output. This allows measuring of the interval
+               between log messages in milliseconds which is useful for
+               identifying timing problems.
+       [/help]
diff --git a/m4/lls/include/logfile.m4 b/m4/lls/include/logfile.m4
new file mode 100644 (file)
index 0000000..e3d40a1
--- /dev/null
@@ -0,0 +1,10 @@
+[option logfile]
+short_opt = L
+arg_info = required_arg
+arg_type = string
+typestr = filename
+summary = where to write log output
+[help]
+       If this option is not given, PROGRAM() writes the log messages
+       to stderr.
+[/help]
diff --git a/m4/lls/include/loglevel.m4 b/m4/lls/include/loglevel.m4
new file mode 100644 (file)
index 0000000..06bea3d
--- /dev/null
@@ -0,0 +1,23 @@
+m4_define(`downcase', `m4_translit(`$*', `A-Z', `a-z')')
+m4_define(`SUITE_LOGLEVELS', `m4_patsubst(`$*', `LL_\([A-Z]+\)',
+       `LSGLL_\1 = "downcase(`\1')" ')')
+[option loglevel]
+       summary = control amount of logging
+       short_opt = l
+       arg_info = required_arg
+       arg_type = string
+       typestr = severity
+       values = {SUITE_LOGLEVELS(LOGLEVELS())}
+       default_val = warning
+       [help]
+               Log only messages with severity greater or equal than the given
+               value. Possible values:
+
+               debug: Produces really noisy output.
+               info: Still noisy, but won't fill up the disk quickly.
+               notice: Indicates normal, but significant event.
+               warning: Unexpected events that can be handled.
+               error: Unhandled error condition.
+               crit: System might be unreliable.
+               emerg: Last message before exit.
+       [/help]
diff --git a/m4/lls/include/long-help.m4 b/m4/lls/include/long-help.m4
new file mode 100644 (file)
index 0000000..408f8e1
--- /dev/null
@@ -0,0 +1,15 @@
+[option long]
+       short_opt = l
+       summary = show the long help text
+[help]
+       If no non-option argument is supplied to the help subcommand and --long
+       is not given, only the names of all subcommands are shown. With --long
+       the purpose of each command is printed as well.
+
+       If the name of a subcommand is supplied and --long is given, the help
+       text for the given subcommand contains the synopsis, the purpose and
+       the description of the specified command, followed by the option list
+       including summary and help text of each option. Without --long the
+       description of the command and the option help are omitted.
+[/help]
+
diff --git a/m4/lls/include/per-command-options-section.m4 b/m4/lls/include/per-command-options-section.m4
new file mode 100644 (file)
index 0000000..a303808
--- /dev/null
@@ -0,0 +1,3 @@
+[option per-command-option-section]
+       summary = Options for PROGRAM()
+       flag ignored
diff --git a/m4/lls/include/port.m4 b/m4/lls/include/port.m4
new file mode 100644 (file)
index 0000000..8dd63b6
--- /dev/null
@@ -0,0 +1,7 @@
+[option port]
+       short_opt = p
+       summary = TCP port to connect to
+       typestr = portnumber
+       arg_info = required_arg
+       arg_type = uint32
+       default_val = 8000
diff --git a/m4/lls/include/priority.m4 b/m4/lls/include/priority.m4
new file mode 100644 (file)
index 0000000..70d44b4
--- /dev/null
@@ -0,0 +1,13 @@
+[option priority]
+       summary = adjust scheduling priority
+       arg_info = required_arg
+       arg_type = int32
+       typestr = prio
+       default_val = 0
+       [help]
+               The priority (also known as nice value) is a value in the range -20
+               to 19. Lower priorities cause more favorable scheduling. Since only
+               privileged processes may request a negative priority, specifying
+               a negative value works only if the daemon is started with root
+               privileges.
+       [/help]
diff --git a/m4/lls/include/sample-format.m4 b/m4/lls/include/sample-format.m4
new file mode 100644 (file)
index 0000000..0daad19
--- /dev/null
@@ -0,0 +1,20 @@
+[option sample-format]
+       short_opt = f
+       summary = specify sample format
+       typestr = format
+       arg_info = required_arg
+       arg_type = string
+       # TODO: dedup this from enum in para.h.
+       values = {
+               SAMPLE_FORMAT_S8 = "S8",
+               SAMPLE_FORMAT_U8 = "U8",
+               SAMPLE_FORMAT_S16_LE = "S16_LE",
+               SAMPLE_FORMAT_S16_BE = "S16_BE",
+               SAMPLE_FORMAT_U16_LE = "U16_LE",
+               SAMPLE_FORMAT_U16_BE = "U16_BE"
+       }
+       default_val = S16_LE
+       [help]
+               It is only necessary to specify this for raw audio. See the discussion
+               of the --channels option.
+       [/help]
diff --git a/m4/lls/include/sample-rate.m4 b/m4/lls/include/sample-rate.m4
new file mode 100644 (file)
index 0000000..1433521
--- /dev/null
@@ -0,0 +1,11 @@
+[option sample-rate]
+       short_opt = s
+       summary = do not guess the input sample rate
+       typestr = rate
+       arg_info = required_arg
+       arg_type = uint32
+       default_val = 44100
+       [help]
+               It is only necessary to specify this for raw audio. See the discussion
+               of the --channels option.
+       [/help]
diff --git a/m4/lls/include/user.m4 b/m4/lls/include/user.m4
new file mode 100644 (file)
index 0000000..6d4d31b
--- /dev/null
@@ -0,0 +1,21 @@
+[option user]
+       short_opt = u
+       summary = run as the given user
+       arg_info = required_arg
+       arg_type = string
+       typestr = username
+       [help]
+               PROGRAM() does not need any special privileges.
+
+               If started as root (EUID == 0) this option must be given at the
+               command line (not in the configuration file) so that PROGRAM()
+               can drop the root privileges right after parsing the command line
+               options, but before parsing the configuration file. In this case,
+               real/effective/saved UID are all set to the UID of <username>. As
+               the configuration file is read afterwards, those options that have
+               a default value depending on the UID (e.g. the directory for the
+               configuration file) are computed by using the uid of <username>. This
+               option has no effect if PROGRAM() is started as a non-root user (i.e.
+               EUID != 0).
+       [/help]
+
diff --git a/m4/lls/include/version.m4 b/m4/lls/include/version.m4
new file mode 100644 (file)
index 0000000..a84ab9d
--- /dev/null
@@ -0,0 +1,3 @@
+[option version]
+       summary = print version and exit
+       short_opt = V
diff --git a/m4/lls/makefile b/m4/lls/makefile
new file mode 100644 (file)
index 0000000..daf6de9
--- /dev/null
@@ -0,0 +1,30 @@
+.PRECIOUS: $(lls_suite_dir)/%.suite $(lsg_h)
+lls_m4_include_dir := $(lls_m4_dir)/include
+
+$(lls_suite_dir)/%.m4d: $(lls_m4_dir)/%.suite.m4 | $(lls_suite_dir)
+       $(call SAY, M4D $<)
+       $(M4) -Pg -I $(lls_m4_include_dir) -s $< \
+       | awk '{if ($$1 ~ /#line/) {gsub(/"/, "", $$3); if ($$3 != "$<") \
+       print "$(lls_suite_dir)/$(*F).suite: " $$3}}' | sort | uniq > $@
+
+$(lls_suite_dir)/%.suite: $(lls_m4_dir)/%.suite.m4 | $(lls_suite_dir)
+       $(call SAY, M4 $<)
+       $(M4) -Pg -I $(lls_m4_include_dir) -D GIT_VERSION=$(GIT_VERSION) \
+               -D COPYRIGHT_YEAR=$(COPYRIGHT_YEAR) -D LOGLEVELS=$(LOGLEVELS) \
+               $< > $@
+
+$(lls_suite_dir)/%.lsg.c: $(lls_suite_dir)/%.suite
+       $(call SAY, LSGC $<)
+       $(LOPSUBGEN) --gen-c --output-dir $(lls_suite_dir) < $<
+
+$(lls_suite_dir)/%.lsg.h: $(lls_suite_dir)/%.suite
+       $(call SAY, LSGH $<)
+       $(LOPSUBGEN) --gen-header --output-dir $(lls_suite_dir) < $<
+
+$(lls_suite_dir)/%.lsg.man: $(lls_suite_dir)/%.suite
+       $(call SAY, LSGM $<)
+       $(LOPSUBGEN) --gen-man --output-dir $(lls_suite_dir) < $<
+
+$(object_dir)/%.o: $(lls_suite_dir)/%.c | $(object_dir)
+       $(call SAY, CC $<)
+       $(CC) -c -o $@ $(CPPFLAGS) $(STRICT_CFLAGS) $<
diff --git a/m4/lls/mixer.suite.m4 b/m4/lls/mixer.suite.m4
new file mode 100644 (file)
index 0000000..3019f76
--- /dev/null
@@ -0,0 +1,235 @@
+m4_define(PROGRAM, para_mixer)
+m4_define(DEFAULT_CONFIG_FILE, ~/.paraslash/mixer.conf)
+[suite mixer]
+version-string = GIT_VERSION()
+caption = List of subcommands
+[supercommand para_mixer]
+       purpose = alarm clock and volume-fader for OSS and ALSA
+       synopsis = [<options>] -- [<subcommand>] [<subcommand_options>]
+       [description]
+               para_mixer adjusts the settings of an audio mixing device. It can
+               set the level of a mixer channel, or fade the level from one value
+               to another in a given time period. The sleep and snooze subcommands
+               contact para_server to start or stop streaming.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(config-file.m4)
+       m4_include(loglevel.m4)
+       m4_include(per-command-options-section.m4)
+       [help]
+               These options apply to several subcommands.
+       [/help]
+       [option mixer-api]
+               short_opt = a
+               summary = select alternative mixer API
+               arg_info = required_arg
+               arg_type = string
+               typestr = api
+               [help]
+                       ALSA is preferred over OSS if both APIs are supported and this option
+                       is not given. To see the supported mixer APIs, use this option with
+                       an invalid string as the mixer API, e.g. --mixer-api help.
+               [/help]
+       [option mixer-device]
+               short_opt = m
+               summary = set mixer device
+               arg_info = required_arg
+               arg_type = string
+               typestr = device
+               [help]
+                       The default device (used if this option is not given) depends
+                       on the selected mixer API. For ALSA, the default is 'hw:0' which
+                       corresponds to the first sound device. For OSS, '/dev/mixer' is used
+                       as the default.
+               [/help]
+       [option mixer-channel]
+               short_opt = C
+               summary = select a mixer channel
+               arg_info = required_arg
+               arg_type = string
+               typestr = channel
+               [help]
+                       For the ALSA mixer API, the possible values are determined at runtime
+                       depending on the hardware and can be printed by specifying an invalid
+                       mixer channel, for example --mixer-channel help. The default channel is
+                       'Master'.
+
+                       For OSS the possible values are invariably 'volume', 'bass', 'treble',
+                       'synth', 'pcm', 'speaker', 'line', 'mic', 'cd', 'imix', 'altpcm',
+                       'reclev', 'igain', 'ogain'. However, not all listed channels might be
+                       supported on any particular hardware. The default channel is 'volume'.
+               [/help]
+       [option fade-exponent]
+               summary = set non-linear time scale for fading
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = value
+               default_val = 0
+               [help]
+                       This option affects the fade, snooze and sleep subcommands. It is
+                       ignored in set mode.
+
+                       The argument must be a number between 0 and 100. The default value
+                       0 corresponds to linear scaling. That is, the value of the mixer
+                       channel is increased or decreased in fixed time intervals until the
+                       destination value is reached. Exponents between 1 and 99 cause low
+                       channel values to be increased more quickly than high channel values.
+                       Large exponents cause greater differences. The special value 100 sets
+                       the destination value immediately. The command then sleeps for the
+                       configured fade time.
+               [/help]
+[subcommand help]
+       purpose = print subcommand help
+       non-opts-name = [<subcommand>]
+       [option long]
+               summary = print the long help text
+               short_opt = l
+
+[subcommand set]
+       purpose = set a channel to the given value and exit
+       [option val]
+               summary = mixer channel value to set
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = value
+               default_val = 10
+[subcommand fade]
+       purpose = fade a channel to the given value in the given time
+       [option fade-vol]
+               short_opt = f
+               summary = destination volume for fading
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = volume
+               default_val = 50
+       [option fade-time]
+               short_opt = t
+               summary = duration of fade period
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = seconds
+               default_val = 5
+[subcommand snooze]
+       purpose = fade out, pause, sleep, play, fade in
+       [option so-time]
+               summary = duration of fade-out period
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = seconds
+               default_val = 30
+       [option so-vol]
+               summary = destination volume for fade-out
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = volume
+               default_val = 20
+       [option snooze-time]
+               summary = delay between end of fade-out and begin of fade-in
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = seconds
+               default_val = 600
+       [option si-time]
+               summary = duration of fade-in period
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = seconds
+               default_val = 180
+       [option si-vol]
+               summary = destination volume for fade-in
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = volume
+               default_val = 80
+[subcommand sleep]
+       purpose = stream, fade out, sleep, fade in
+       [description]
+               Change to the initial volume and select the initial mood/playlist.
+               Fade out to the given fade-out volume in the specified time. Switch
+               to the sleep mood/playlist and wait until wake time minus fade-in
+               time. Finally, switch to the wake mood/playlist and fade in to the
+               fade-in volume.
+       [/description]
+       [option ivol]
+               summary = set initial volume
+               arg_info = required_arg
+               arg_type = string
+               default_val = 60
+               flag multiple
+               typestr = [channel:]volume
+               [help]
+                       Used as the start volume, before fading out to the fade-out volume. The
+                       channel part may be omitted, in which case the default channel is
+                       used. This option may be given multiple times.
+               [/help]
+       [option fo-mood]
+               summary = mood or playlist for fade-out
+               arg_info = required_arg
+               arg_type = string
+               typestr = mood_spec
+               [help]
+                       This mood (or playlist) is selected right after setting the initial
+                       volume.
+               [/help]
+       [option fo-time]
+               summary = duration of fade-out period
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = seconds
+               default_val = 1800
+               [help]
+                       No fading if this is set to 0.
+               [/help]
+       [option fo-vol]
+               summary = destination volume for fade-out
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = volume
+               default_val = 20
+       [option sleep-mood]
+               summary = mood/playlist between fade-out and fade-in
+               arg_info = required_arg
+               arg_type = string
+               typestr = mood_spec
+               [help]
+                       Select the given mood or playlist after the fade-out. If unset,
+                       playback is stopped until fade-in starts.
+               [/help]
+       [option wake-time]
+               short_opt = w
+               summary = when to start fade in
+               arg_info = required_arg
+               arg_type = string
+               typestr = [+][HH][:MM]
+               default_val = +9:00
+               [help]
+                       If the optional plus character is given, the wake time is computed as
+                       now + HH hours + MM minutes. Otherwise the HH:MM argument is considered
+                       an absolute time (referring to either the current or the next day).
+               [/help]
+       [option fi-mood]
+               summary = mood or playlist for fade-in
+               arg_info = required_arg
+               arg_type = string
+               typestr = mood_spec
+               [help]
+                       This mood or playlist is selected right before fade-in begins.
+               [/help]
+       [option fi-time]
+               summary = duration of fade-in period
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = seconds
+               default_val = 1200
+               [help]
+                       No fading if this is set to 0.
+               [/help]
+       [option fi-vol]
+               summary = destination volume for fade-in
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = volume
+               default_val = 80
diff --git a/m4/lls/play.suite.m4 b/m4/lls/play.suite.m4
new file mode 100644 (file)
index 0000000..f2220f1
--- /dev/null
@@ -0,0 +1,51 @@
+m4_define(PROGRAM, para_play)
+m4_define(DEFAULT_HISTORY_FILE, ~/.paraslash/play.history)
+m4_define(DEFAULT_CONFIG_FILE,~/.paraslash/play.conf)
+[suite play]
+version-string = GIT_VERSION()
+[supercommand para_play]
+       purpose = command line audio player
+       non-opts-name = <audio_file>...
+       [description]
+               para_play operates either in command mode or in insert mode. In insert
+               mode it presents a prompt and allows the user to enter commands like
+               play, pause, quit, etc. In command mode the current audio file and the
+               playback position are shown instead of the prompt/command line, and
+               the program reads single key strokes from stdin. Keys may be mapped
+               to commands so that the configured command is executed whenever a
+               mapped key is pressed.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       m4_include(config-file.m4)
+       m4_include(history-file.m4)
+       m4_include(per-command-options-section.m4)
+       [option randomize]
+               short_opt = z
+               summary = randomize playlist at startup
+       [option end-of-playlist]
+               summary = what to do after the last file has been played
+               arg_info = required_arg
+               arg_type = string
+               typestr = behaviour
+               values = {
+                       EOP_LOOP = "loop",
+                       EOP_STOP = "stop",
+                       EOP_QUIT = "quit"
+                }
+       [option key-map]
+               short_opt = k
+               summary = map a key to a command
+               arg_info = required_arg
+               arg_type = string
+               flag multiple
+               typestr = key:command [args]
+               [help]
+                       This option may be given multiple times, one for each key
+                       mapping. Example:
+
+                               --key-map 5:jmp 50
+               [/help]
diff --git a/m4/lls/play_cmd.suite.m4 b/m4/lls/play_cmd.suite.m4
new file mode 100644 (file)
index 0000000..36d475d
--- /dev/null
@@ -0,0 +1,93 @@
+[suite play_cmd]
+caption = list of commands
+[subcommand help]
+       purpose = list commands or keybindings, or print command-specific help
+       non-opts-name = [command]
+       [description]
+
+               This command acts differently depending on whether it is executed in
+               command mode or in insert mode.
+
+               In command mode, the help command prints the list of defined
+               keybindings. In insert mode, if no argument is given, it prints the
+               list of available commands. When called with the name of a command
+               as first argument, it prints the description of this command.
+       [/description]
+       m4_include(`long-help.m4')
+
+[subcommand fg]
+       purpose = enter command mode
+       [description]
+               In this mode, file name and play time of the current audio file are
+               displayed. Hit CTRL+C or : to switch to input mode.
+       [/description]
+[subcommand next]
+       purpose = load the next file of the playlist
+
+[subcommand prev]
+       purpose = load the previous file of the playlist
+
+[subcommand bg]
+       purpose = enter insert mode
+       [description]
+               Only useful if called in command mode via a key binding. The default
+               key bindings map the colon key to this command, so pressing : in
+               command mode activates insert mode.
+       [/description]
+
+[subcommand jmp]
+       purpose = change playback position
+       non-opts-name = percent
+       [description]
+               The percent argument must be an integer between 0 and 100, inclusively.
+       [/description]
+
+[subcommand ff]
+       purpose = set playback position relative to the current position
+       non-opts-name = seconds
+       [description]
+               Negative values mean to jump backwards the given amount of seconds.
+       [/description]
+
+[subcommand ls]
+       purpose = list the playlist
+       [description]
+               This prints all paths of the playlist. The currently
+               active file is marked with an asterisk.
+       [/description]
+
+[subcommand info]
+       purpose = print information about the current file
+       [description]
+               The output contains the playlist position, the path
+               and information provided by the audio format handler.
+       [/description]
+
+[subcommand play]
+       purpose = start or resume playback
+       non-opts-name = [num]
+       [description]
+               If no argument is given, playback starts at the current
+               position. Otherwise, the corresponding file is loaded
+               and playback is started at the beginning of the file.
+       [/description]
+
+[subcommand pause]
+       purpose = suspend playback
+       [description]
+               When paused, it is still possible to jump around in
+               the file via the jmp and ff commands.
+       [/description]
+
+[subcommand tasks]
+       purpose = print list of active tasks
+       [description]
+               Mainly useful for debugging.
+       [/description]
+
+[subcommand quit]
+       purpose = exit para_play
+       [description]
+               Pressing CTRL+D causes EOF on stdin which also exits
+               para_play.
+       [/description]
diff --git a/m4/lls/recv.suite.m4 b/m4/lls/recv.suite.m4
new file mode 100644 (file)
index 0000000..993a449
--- /dev/null
@@ -0,0 +1,33 @@
+m4_define(PROGRAM, para_recv)
+[suite recv]
+version-string = GIT_VERSION()
+[supercommand para_recv]
+       purpose = receive an audio stream
+       [description]
+               para_recv starts one paraslash receiver (http, dccp, udp or afh)
+               to produce an audio stream in the same way para_audiod would download
+               the stream from para_server (http, dccp or udp) or para_server makes a
+               stream out of an audio file (afh). This is mostly useful for debugging.
+
+               Regardless of which receiver was started, the audio stream is written
+               to stdout.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       m4_include(per-command-options-section.m4)
+       [option receiver]
+               short_opt = r
+               summary = select the receiver
+               arg_info = required_arg
+               arg_type = string
+               typestr = receiver_spec
+               [help]
+                       Any options for the selected receiver must be quoted. Example:
+
+                               -r 'http -i www.paraslash.org -p 8009'
+
+                       If no receiver is given, http is assumed.
+               [/help]
diff --git a/m4/lls/recv_cmd.suite.m4 b/m4/lls/recv_cmd.suite.m4
new file mode 100644 (file)
index 0000000..de6c5a6
--- /dev/null
@@ -0,0 +1,102 @@
+[suite recv_cmd]
+caption = receivers
+[subcommand afh]
+       purpose = make an audio stream from a local file
+       [description]
+               The afh (audio format handler) receiver extracts selected parts of
+               the given audio file without decoding the file. Only complete chunks
+               with respect to the underlying audio format are extracted.
+       [/description]
+       [option filename]
+               short_opt = f
+               summary = file to open
+               typestr = filename
+               arg_info = required_arg
+               arg_type = string
+       [option begin-chunk]
+               short_opt = b
+               summary = skip the beginning of the file
+               typestr = chunk_num
+               arg_info = required_arg
+               arg_type = int32
+               [help]
+                       The argument must be an integer between -num_chunks and num_chunks -
+                       1, inclusively, where num_chunks is the total number of chunks. If
+                       chunk_num is negative, the given number of chunks are counted backwards
+                       from the end of the file. For example --begin-chunk -100 instructs
+                       the afh receiver to start at chunk num_chunks - 100. This is useful
+                       for cutting off the beginning of an audio file.
+               [/help]
+       [option end-chunk]
+               short_opt = e
+               summary = only write up to chunk chunk_num
+               typestr = chunk_num
+               arg_info = required_arg
+               arg_type = int32
+               [help]
+                       For the chunk_num argument the same rules as for --begin-chunk
+                       apply. The default is to write up to the last chunk.
+               [/help]
+       [option just-in-time]
+               short_opt = j
+               summary = use timed writes
+               [help]
+                       Write the specified data chunks 'just in time', i.e., delay the write
+                       until data is needed by the decoder/player for an uninterrupted audio
+                       stream. This may be useful for third-party software.
+               [/help]
+       [option no-header]
+               short_opt = h
+               summary = do not write an audio file header
+               [help]
+                       Some audio formats store information about the audio file in
+                       a format-specific header which is needed to decode any part of
+                       the file. For such formats the afh receiver generates a suitable
+                       header. This option changes the default behaviour, i.e. no header
+                       is written.
+               [/help]
+[subcommand http]
+       purpose = receive an audio stream over HTTP
+       m4_include(host.m4)
+       m4_include(port.m4)
+[subcommand dccp]
+       purpose = receive an audio stream over DCCP
+       m4_include(host.m4)
+       m4_include(port.m4)
+       [option ccid]
+               short_opt = c
+               summary = CCID preference(s) for this connection
+               typestr = id
+               arg_info = required_arg
+               arg_type = uint32
+               flag multiple
+               [help]
+                       When present exactly once, this option mandates the CCID for the
+                       sender-receiver connection. If it is passed more than once, it sets
+                       a preference list where the order of appearance signifies descending
+                       priority. For example, passing 4, 2, 3 creates the preference list
+                       (CCID-4, CCID-2, CCID-3), assigning CCID-4 highest preference.
+
+                       The request is reconciled with the CCIDs on the server through the
+                       'server-priority' mechanism of RFC 4340 6.3.1/10. The server CCIDs
+                       can be listed by calling 'para_client sender dccp status'.
+               [/help]
+[subcommand udp]
+       purpose = receive an audio stream over UDP
+       [option host]
+               short_opt = i
+               summary = IP address or hostname
+               typestr = host
+               arg_info = required_arg
+               arg_type = string
+               default_val = 224.0.1.38
+               [help]
+                       The default address resolves to DANTZ.MCAST.NET and activates
+                       multicast.
+               [/help]
+       m4_include(port.m4)
+       [option iface]
+               summary = receiving udp multicast interface
+               typestr = iface-name
+               arg_info = required_arg
+               arg_type = string
diff --git a/m4/lls/server.suite.m4 b/m4/lls/server.suite.m4
new file mode 100644 (file)
index 0000000..93e4b57
--- /dev/null
@@ -0,0 +1,411 @@
+m4_define(PROGRAM, para_server)
+m4_define(DEFAULT_CONFIG_FILE, ~/.paraslash/server.conf)
+[suite server]
+version-string = GIT_VERSION()
+[supercommand para_server]
+       purpose = manage and stream audio files
+       [description]
+               para_server streams audio files over a local or remote network. It
+               is controlled by para_client(1), which connects para_server through
+               the paraslash control service.
+
+               On startup the server spawns a second process, the audio file selector,
+               which maintains the database of all known audio files. This database
+               contains file format, duration and tag information of each known file
+               and statistics such as last-played time, and the number of times each
+               file was streamed. Lyrics and cover art may be added to the database
+               and associated with one or more audio files.
+
+               Besides ordinary playlists the audio file selector supports so-called
+               moods. Moods instruct the server to determine the files to be streamed
+               and their order in terms of properties stored in the database.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(config-file.m4)
+       m4_include(priority.m4)
+       m4_include(daemon.m4)
+       m4_include(logfile.m4)
+       m4_include(user.m4)
+       m4_include(group.m4)
+       m4_include(loglevel.m4)
+       m4_include(log-timing.m4)
+       m4_include(color.m4)
+       m4_include(per-command-options-section.m4)
+       [option listen-address]
+               summary = local listening addresses for the control service
+               arg_info = required_arg
+               arg_type = string
+               typestr = addr
+               flag multiple
+               [help]
+                       para_server listens on a TCP socket for incoming connections from
+                       para_client or para_audiod. This option controls on which addresses
+                       the server should listen. If the option is not given, the server
+                       listens on all local addresses (INADDR_ANY for IPv4 addresses,
+                       IN6ADDR_ANY_INIT for IPv6 addresses).
+
+                       The argument specifies an IPv4 or an IPv6 address, either a numerical
+                       network address (for IPv4, numbers-and-dots notation as supported
+                       by inet_aton(3); for IPv6, hexadecimal string format as supported
+                       by inet_pton(3)), or a network hostname, whose network addresses is
+                       looked up and resolved. The address can optionally include a port
+                       number. For addresses for which no port number is given, the argument
+                       of the --port option (see below) is implied.
+
+                       This option may be given multiple times. The server will then listen
+                       on each of the specified addresses.
+
+                       Examples: 10.10.1.1, 10.10.1.2:2991, localhost, localhost:2991,
+                       [::1]:2991, [badc0de::1].
+               [/help]
+       [option port]
+               short_opt = p
+               summary = listening port of the control service
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = portnumber
+               default_val = 2990
+               [help]
+                       This option applies only to addresses given to --listen-address
+                       (see above) which do no include a port number. If the default port
+                       is changed, the corresponding option of para_client must be used to
+                       connect to para_server.
+               [/help]
+       [option user-list]
+               summary = file which contains user names and credentials
+               arg_info = required_arg
+               arg_type = string
+               typestr = path
+               [help]
+                       This file contains one line per user of the form
+
+                               user <username> <key> <perms>
+
+                       See the manual for more information.
+               [/help]
+       [option vss]
+               summary = Options for the virtual streaming system
+               flag ignored
+       [option autoplay]
+               summary = start streaming on startup
+               short_opt = a
+               [help]
+                       The default is to defer streaming until para_client connects and
+                       executes the "play" command.
+               [/help]
+       [option autoplay-delay]
+               summary = time to wait before streaming
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = milliseconds
+               default_val = 0
+               [help]
+                       This option is ignored if --autplay is not given. Otherwise, its
+                       argument defines for how long streaming is delayed at startup.
+
+                       This is useful in init scripts to set the delay large enough to make
+                       sure para_audiod is up when para_server starts to stream.
+               [/help]
+       [option announce-time]
+               short_opt = A
+               summary = grace time for data connections
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = milliseconds
+               default_val = 300
+               [help]
+                       para_server tells para_audiod through the control service connection
+                       whether an audio stream is currently available. This option defines
+                       the delay between announcing the stream and sending the first chunk
+                       of audio data.
+               [/help]
+       [option afs]
+               summary = Options for the audio file selector
+               flag ignored
+       [option afs-database-dir]
+               summary = location of the afs database
+               short_opt = D
+               arg_info = required_arg
+               arg_type = string
+               typestr = directory
+               [help]
+                       The directory which contains the database for the audio file
+                       selector. The default is ~/.paraslash/afs_database-0.4.
+
+                       If no database was found, the "init" command must be executed to
+                       initialize the database. Once initialized, audio files may added with
+                       the "add" command.
+               [/help]
+       [option afs-socket]
+               summary = socket for afs connections
+               short_opt = s
+               arg_info = required_arg
+               arg_type = string
+               typestr = path
+               default_val = /var/paraslash/afs_command_socket-0.4
+               [help]
+                       Server commands communicate with the audio file selector, via a
+                       local socket. This option specifies the location of the socket in
+                       the file system.
+               [/help]
+       [option afs-initial-mode]
+               summary = mood or playlist to load on startup
+               short_opt = i
+               arg_info = required_arg
+               arg_type = string
+               typestr = specifier/name
+               [help]
+                       The argument of this option consists of a prefix, either 'm/' or
+                       'p/', to indicate whether a mood or a playlist should be loaded,
+                       followed by the name of the mood or playlist. Example:
+
+                               --afs-initial-mode p/foo
+
+                       loads the playlist named "foo".
+
+                       If this option is not given, the dummy mood is loaded at startup.
+               [/help]
+       [option http]
+               summary = Options for the http sender
+               flag ignored
+       [option http-listen-address]
+               summary = listening addresses of the http sender
+               arg_info = required_arg
+               arg_type = string
+               typestr = addr
+               flag multiple
+               [help]
+                       The http sender of para_server listens on this port for incoming data
+                       connections. This option controls on which addresses the http sender
+                       should listen. See the documentation of the --listen-address above
+                       for the format of the address argument and the defaults.
+               [/help]
+       [option http-port]
+               summary = TCP port for http streaming
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = portnumber
+               default_val = 8000
+               [help]
+                       This option has the same meaning as --port, but applies to http
+                       data connections and applies to the addresses specified as arguments
+                       to --http-listen-address.
+               [/help]
+       [option http-default-deny]
+               summary = make the http access control list a whitelist
+               [help]
+                       The default is to use blacklists, i.e. connections to the http sender
+                       are allowed unless the connecting host matches a pattern given by
+                       a http-access option. This option allows using access control lists
+                       the other way round: Connections are denied from hosts which are not
+                       explicitly allowed by one or more http-access options.
+               [/help]
+       [option http-access]
+               summary = add an entry to the http access control list
+               arg_info = required_arg
+               arg_type = string
+               typestr = a.b.c.d/n
+               flag multiple
+               [help]
+                       Add the given host/network to access control list (whitelist if
+                       http-default-deny was given, blacklist otherwise) before opening
+                       the tcp port. This option can be given multiple times. Example:
+
+                               --http-access 192.168.0.0/24
+
+                       whitelists/blacklists the 256 hosts 192.168.0.x.
+
+                       This option may be given multiple times to blacklist/whitelist any
+                       number of hosts or networks.
+               [/help]
+       [option http-no-autostart]
+               summary = do not open TCP port for http streaming on startup
+               [help]
+                       If this option is given, the http sender does not listen on its TCP
+                       port until the "sender" command is executed to open the port.
+               [/help]
+       [option http-max-clients]
+               summary = maximal number of simultaneous http connections
+               arg_info = required_arg
+               arg_type = int32
+               typestr = number
+               default_val = -1
+               [help]
+                       The http sender will refuse connections if already that number of
+                       clients are currently connected. A non-positive value (the default)
+                       allows for an unlimited number of simultaneous connections.
+               [/help]
+       [option dccp]
+               summary = Options for the dccp sender
+               flag ignored
+       [option dccp-listen-address]
+               summary = listening addresses of the dccp sender
+               arg_info = required_arg
+               arg_type = string
+               typestr = addr
+               flag multiple
+               [help]
+                       Like --http-listen-address, but for the dccp sender.
+               [/help]
+       [option dccp-port]
+               summary = port for dccp streaming
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = portnumber
+               default_val = 8000
+               [help]
+                       See --http-port for details.
+               [/help]
+       [option dccp-default-deny]
+               summary = make the dccp access control list a whitelist
+               [help]
+                       See http-default-deny for details.
+               [/help]
+       [option dccp-access]
+               summary = add an entry to the dccp access control list
+               arg_info = required_arg
+               arg_type = string
+               typestr = a.b.c.d/n
+               flag multiple
+               [help]
+                       See --http-access for details.
+               [/help]
+       [option dccp-no-autostart]
+               summary = do not open the DCCP port on startup
+               [help]
+                       This is like --http-no-autostart but applies to the dccp sender.
+               [/help]
+       [option dccp-max-clients]
+               summary = maximal number of simultaneous dccp connections
+               arg_info = required_arg
+               arg_type = int32
+               typestr = number
+               default_val = -1
+               [help]
+                       See --http-max-clients for details.
+               [/help]
+       [option dccp-max-slice-size]
+               summary = upper bound for the FEC slice size
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = bytes
+               default_val = 0
+               [help]
+                       If this value is zero (the default) the dccp sender uses the maximum
+                       packet size (MPS) of the connection as the slice size. The MPS is a
+                       network parameter and depends on the path maximum transmission unit
+                       (path MTU) of an incoming connection, i.e. on the largest packet size
+                       that can be transmitted without causing fragmentation.
+
+                       This option allows values less than the MPS in order to fine-tune
+                       application performance. Values greater than the MPS of an incoming
+                       connection can not be set.
+               [/help]
+       [option dccp-data-slices-per-group]
+               summary = the number of non-redundant slices per FEC group
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = count
+               default_val = 3
+               [help]
+                       This determines the number of slices in each FEC group which are
+                       necessary to decode the group. The given number must be smaller than
+                       the argument to the --dccp-slices-per-group option below.
+
+                       Note that the duration of a FEC group is proportional to the
+                       product dccp-max-slice-size * dccp-data-slices-per-group.
+               [/help]
+       [option dccp-slices-per-group]
+               summary = the total number of slices per FEC group
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = count
+               default_val = 4
+               [help]
+                       This value must be larger than the value of the argument to
+                       --dccp-data-slices-per-group. The difference of the two values is
+                       the number of redundant slices, that is, the number of slices which
+                       may be lost without causing interruptions in the audio stream.
+
+                       Increase this value if you are on a lossy network.
+               [/help]
+       [option udp]
+               summary = Options for the udp sender
+               flag ignored
+       [option udp-target]
+               summary = add udp target with optional port
+               arg_info = required_arg
+               arg_type = string
+               typestr = host[:port]
+               flag multiple
+               [help]
+                       Add the given host/port to the list of targets. The "host" argument
+                       can be either an IPv4/v6 address or hostname (RFC 3986 syntax). The
+                       "port" argument is an optional port number. If the "port" part is
+                       absent, the "--udp-default-port" value (see below) is used.
+
+                       The following examples are possible targets: "10.10.1.2:8000"
+                       (host:port); "10.10.1.2" (with default port); "224.0.1.38:1500"
+                       (IPv4 multicast); "localhost:8001" (hostname with port); "[::1]:8001"
+                       (IPv6 localhost); "[badc0de::1]" (IPv6 host with default port);
+                       "[FF00::beef]:1500" (IPv6 multicast).
+
+                       This option can be given multiple times, for multiple targets.
+               [/help]
+       [option udp-default-port]
+               summary = default port for udp targets
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = portnumber
+               default_val = 8000
+       [option udp-no-autostart]
+               summary = do not send the audio stream to UDP targets
+               [help]
+                       If this option is given, udp streaming may be activated at a later
+                       time by executing the "sender" command.
+               [/help]
+       [option udp-mcast-iface]
+               summary = outgoing udp multicast interface
+               arg_info = required_arg
+               arg_type = string
+               typestr = interface
+       [option udp-header-interval]
+               short_opt = H
+               summary = duration for sending header
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = milliseconds
+               default_val = 2000
+               [help]
+                       As the udp sender has no idea about connected clients it sends the
+                       audio file header periodically if necessary. This option specifies the
+                       duration between subsequent headers are sent. Smaller values decrease
+                       the average time clients have to wait before starting playback,
+                       larger values decrease network traffic.
+
+                       Note that this affects only ogg/* and wma streams. Other audio formats,
+                       including mp3, don't need an audio file header.
+               [/help]
+       [option udp-ttl]
+               short_opt = t
+               summary = set time to live value
+               arg_info = required_arg
+               arg_type = int32
+               typestr = num
+               default_val = -1
+               [help]
+                       This option applies exclusively to multicast UDPv4/v6 streaming.
+
+                       For the sending UDPv4 socket it sets the multicast Time-To-Live value
+                       to "num".  Traditional TTL scope values are: 0=host, 1=network, 32=same
+                       site, 64=same region, 128=same continent, 255=unrestricted. Please
+                       note however that this scoping is not a good solution: RFC 2365
+                       e.g. presents a better alternative.
+
+                       When using UDPv6 multicasting, the option sets the number of multicast
+                       hops (as described in RFC 3493); a value of -1 allows the kernel to
+                       auto-select the hop value.
+               [/help]
diff --git a/m4/lls/server_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4
new file mode 100644 (file)
index 0000000..2a39907
--- /dev/null
@@ -0,0 +1,702 @@
+[suite server_cmd]
+caption = list of server commands
+aux_info_prefix = Permissions:
+
+[introduction]
+       The server process listens on a network socket and accepts connections
+       from para_client or para_audiod. For the connection to succeed the
+       connecting peer must authenticate as one of the users stored in the
+       user table of para_server. Each entry of the user table contains the
+       set of permission bits that are granted to the user. Authenticated
+       users may execute one of the commands below if the set of permission
+       bits of the command is a subset of the permission bits that are
+       granted to the user.
+[/introduction]
+
+[subcommand add]
+       purpose = add or update audio files
+       non-opts-name = path...
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               Each path must be absolute and refer to either an audio file or a
+               directory. In case of a directory, all audio files in that directory
+               are added recursively. Note that the given paths refer to files or
+               directories on the host on which para_server is running.
+       [/description]
+       [option all]
+               short_opt = a
+               summary = add all files
+               [help]
+                       The default is to add only files ending in a known suffix for a
+                       supported audio format.
+               [/help]
+       [option lazy]
+               short_opt = l
+               summary = add files lazily
+               [help]
+                       If the path already exists in the database, skip this file. This
+                       operation is really cheap. Useful to update large directories after
+                       some files have been added.
+               [/help]
+       [option force]
+               short_opt = f
+               summary = force adding/updating
+               [help]
+                       Recompute the audio format handler data even if a file with the same
+                       path and the same hash value exists.
+               [/help]
+       [option verbose]
+               short_opt = v
+               summary = enable verbose mode
+               [help]
+                       Print what is being done.
+               [/help]
+
+[subcommand addatt]
+       purpose = add new attribute(s)
+       non-opts-name = attribute...
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               This adds new attributes to the attribute table. At most 64 attributes
+               may be defined.
+       [/description]
+
+[subcommand check]
+       purpose = run integrity checks on database tables
+       aux_info = AFS_READ
+       [description]
+               If no options are given, all checks are run.
+       [/description]
+       [option aft]
+               short_opt = a
+               summary = run audio file table checks
+               [help]
+                       Report stale paths and invalid image and lyrics ids of the audio
+                       file table.
+               [/help]
+       [option attribute]
+               short_opt = A
+               summary = check for invalid attributes
+               [help]
+                       Report audio files whose attribute bitmask is invalid, i.e., has a bit
+                       set which does not correspond to any attribute of the attribute table.
+               [/help]
+       [option mood]
+               short_opt = m
+               summary = check for invalid mood definitions
+               [help]
+                       Run syntax checks on all moods of the mood table.
+               [/help]
+       [option playlist]
+               short_opt = p
+               summary = find invalid paths in playlists
+               [help]
+                       Check all playlists for paths not contained in the audio file table.
+               [/help]
+
+[subcommand cpsi]
+       purpose = copy selected parts of the audio file selector info
+       non-opts-name = source pattern...
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               If no option, or only --verbose is given, all fields of the audio
+               file selector info structure are copied to each row of the audio file
+               table whose path matches at least one of the given patterns. Otherwise,
+               only those fields which correspond to the given options are copied.
+       [/description]
+       [option attribute-bitmap]
+               short_opt = a
+               summary = copy the attribute bitmap
+       [option image-id]
+               short_opt = i
+               summary = copy the image id
+       [option lyrics-id]
+               short_opt = y
+               summary = copy the lyrics id
+       [option lastplayed]
+               short_opt = l
+               summary = copy the lastplayed timestamp
+       [option numplayed]
+               short_opt = n
+               summary = copy the numplayed counter
+       [option verbose]
+               short_opt = v
+               summary = enable verbose mode
+
+[subcommand ff]
+       purpose = jump forward or backward in the current audio file
+       synopsis = seconds
+       aux_info = VSS_READ | VSS_WRITE
+       [description]
+               This enqueues a request to reposition the audio stream according to
+               the argument, which may be a signed or an unsigned integer. Negative
+               values correspond to backward jumps.
+
+               If a negative number is given whose absolute value exceeds the current
+               postition of the stream, a jump to the beginning of the audio file
+               is performed. If a positive amount of seconds is given which exceeds
+               the remaining time of the audio file, the next audio file is loaded.
+
+       [/description]
+
+[subcommand help]
+       purpose = list available commands or print command-specific help
+       non-opts-name = [command]
+       aux_info = NO_PERMISSION_REQUIRED
+       [description]
+               When executed without any arguments, the available server commands
+               are listed. Otherwise, if the first argument is the name of a server
+               command, the description of this command is shown.
+       [/description]
+       m4_include(`long-help.m4')
+
+[subcommand hup]
+       purpose = reload config file, log file and user list
+       aux_info = VSS_WRITE
+       [description]
+               Reread the config file and the user list file, close and reopen the log
+               file, and ask the afs process to do the same. Sending the HUP signal
+               to the server process has the same effect as running this command.
+       [/description]
+
+[subcommand init]
+       purpose = initialize the database tables for the audio file selector
+       synopsis = [table_name...]
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               When invoked without arguments, this command creates all
+               tables: audio_files, attributes, scores, moods, lyrics, images,
+               playlists. Otherwise only the given tables are created.
+       [/description]
+
+[subcommand jmp]
+       purpose = reposition the current stream
+       non-opts-name = n
+       aux_info = VSS_READ | VSS_WRITE
+       [description]
+               Set the 'R' (reposition request) bit of the vss status flags and
+               enqueue a request to jump to n% of the current audio file, where 0 <=
+               n <= 100.
+       [/description]
+
+[subcommand ls]
+       purpose = list audio files which match a pattern
+       non-opts-name = [pattern...]
+       aux_info = AFS_READ
+       [description]
+               If no pattern is given, all files are listed.  Otherwise, the command
+               lists all files of the audio file table whose path matches at least
+               one of the given patterns.
+       [/description]
+       [option listing-mode]
+               short_opt = l
+               summary = use alternative output format
+               arg_type = string
+               arg_info = optional_arg
+               typestr = mode
+               default_val = long
+               [help]
+                       The optional mode argument is either a single character or a word
+                       according to the following list.
+
+                       short (s). List only the path or basename (last component of the path),
+                       depending on whether -p is also given. This listing mode acts as if
+                       --listing-mode had not been given.
+
+                       long (l). Show detailed information. This is the default if no argument
+                       to --listing-mode is supplied.
+
+                       verbose (v). Multi-line output, one row per data field stored in the
+                       audio file table.
+
+                       parser-friendly (p). Like verbose listing mode, but use numerical
+                       values for the names of the output fields and prefix each line with
+                       a length field.
+
+                       mbox (m). Generate output suitable to be viewed with a mail
+                       program. One "mail" per matching audio file.
+
+                       chunk-table (c). Print path (or basename, depending on whether -p is
+                       also given), chunk time and chunk offsets.
+
+               [/help]
+       [option basename]
+               short_opt = b
+               summary = list and match basenames only
+               [help]
+                       Print only the basename of each matching file and match only the
+                       basenames of the paths stored in the audio file table against the
+                       given patterns. The default is to print and match the full path.
+               [/help]
+       [option admissible]
+               short_opt = a
+               summary = list only admissible files
+               [help]
+                       List only files which are admissible with respect to the current mood
+                       or playlist.
+               [/help]
+       [option reverse]
+               short_opt = r
+               summary = reverse sort order
+       [option unix-date]
+               short_opt = d
+               summary = print dates as seconds after the epoch
+       [option sort]
+               short_opt = s
+               summary = change sort order
+               arg_type = string
+               arg_info = required_arg
+               typestr = order
+               default_val = path
+               [help]
+                       The sort order must be given as an required argument. Like for
+                       --listing-mode, this argument may either be a single character or a
+                       word, according to the following list.
+
+                       path (p). Sort alphabetically by path or basename, depending on
+                       whether -b is given. This is the default if --sort is not given.
+
+                       score (s). Iterate over the entries of the score table, rather than
+                       the audio file table. This sort order implies --admissible, since
+                       the score table contains only admissible files.
+
+                       lastplayed (l)
+
+                       numplayed (n)
+
+                       frequency (f)
+
+                       channels (c)
+
+                       image-id (i)
+
+                       lyrics-id (y)
+
+                       bitrate (b)
+
+                       duration (d)
+
+                       audio-format (a)
+
+                       hash (h)
+
+                       If --sort is not given, path sort is implied.
+               [/help]
+
+[subcommand lsatt]
+       purpose = list attributes
+       aux_info = AFS_READ
+       [description]
+               Print the list of all defined attributes which match the given
+               pattern. If no pattern is given, the full list is printed.
+       [/description]
+
+       [option id-sort]
+               short_opt = i
+               summary = sort attributes by id
+               [help]
+                       The default is to sort alphabetically by name.
+
+                       Attributes are internally represented as an 64 bit array. The attribute
+                       id is the bit number in this array.
+               [/help]
+       [option long]
+               short_opt = l
+               summary = print long listing
+               [help]
+                       The long listing prints the attribute id in addition to the name of
+                       the attribute. The id is printed as a decimal number and is separated
+                       from the name by a tab character.
+               [/help]
+       [option reverse]
+               short_opt = r
+               summary = reverse sort order
+
+[subcommand mvatt]
+       purpose = rename an attribute
+       synopsis = source dest
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               Rename the attribute given by the first argument to the destination
+               given by the second argument. It is an error if the destination
+               attribute exists.
+       [/description]
+
+[subcommand next]
+       purpose = close the stream and start to stream the next audio file
+       aux_info = VSS_READ | VSS_WRITE
+       [description]
+               Set the 'N' (next audio file) bit of the vss status flags. This
+               instructs the server to close the current stream, if any. The 'P'
+               (playing) bit is not modified by this command. If it is on, playing
+               continues with the next audio file.
+
+               This command is equivalent to stop if paused, and has no effect
+               if stopped.
+       [/description]
+
+[subcommand nomore]
+       purpose = stop playing after current audio file
+       aux_info = VSS_READ | VSS_WRITE
+       [description]
+               Set the 'O' (no more) bit of the vss status flags which asks
+               para_server to clear the 'P' (playing) bit after the 'N' (next audio
+               file) bit transitions from off to on (because the end of the current
+               audio file is reached). Use this command instead of stop if you don't
+               like sudden endings.
+       [/description]
+
+[subcommand pause]
+       purpose = suspend the current stream
+       aux_info = VSS_READ | VSS_WRITE
+       [description]
+               Clear the 'P' (playing) bit of the vss status flags.
+       [/description]
+
+[subcommand play]
+       purpose = start or resume playback
+       aux_info = VSS_READ | VSS_WRITE
+       [description]
+               Set the 'P' (playing) bit of the vss status flags.
+       [/description]
+
+[subcommand rm]
+       purpose = remove rows from the audio file table
+       non-opts-name = pattern...
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               Remove all rows of the audio file table which match any of the given
+               patterns. Note that this affects only the database table; the command
+               won't touch your audio files on disk.
+       [/description]
+       [option verbose]
+               short_opt = v
+               summary = print paths of deleted rows
+       [option force]
+               short_opt = f
+               summary = don't complain if nothing was removed
+       [option pathname-match]
+               short_opt = p
+               summary = modify matching behaviour
+               [help]
+                       Match a slash in the path only with a slash in pattern and not by an
+                       asterisk (*) or a question mark (?) metacharacter, nor by a bracket
+                       expression ([]) containing a slash (see fnmatch(3)).
+               [/help]
+
+[subcommand rmatt]
+       purpose = remove attribute(s)
+       non-opts-name = pattern...
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               Remove all attributes which match any given pattern. All information
+               about the removed attributes in the audio file table is lost.
+       [/description]
+
+[subcommand select]
+       purpose = activate a mood or a playlist
+       non-opts-name = specifier/name
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               The specifier is either 'm' or 'p' to indicate whether a playlist or
+               a mood should be activated. Example:
+
+                       select m/foo
+
+               activates the mood named 'foo'.
+       [/description]
+
+[subcommand sender]
+       purpose = control paraslash senders
+       synopsis = [sender subcmd [arguments]]
+       aux_info = VSS_READ | VSS_WRITE
+       [description]
+               This command executes a subcommand for the given sender, which is
+               one of "http", "dccp" or "udp". Various subcommands exist to print
+               information about the sender, to activate and deactivate the sender,
+               and to change the access permissions and targets. The following
+               subcommands are available:
+
+                      help, status, on, off, allow, deny, add, delete.
+
+               All senders support the first four commands. The "allow" and "deny"
+               commands are supported by the http and the dccp senders while "add"
+               and "delete" are only supported by the udp sender. If no sender is
+               given, the list of available senders is shown.
+
+               Examples:
+
+               Get help for the udp sender (contains further examples):
+
+                       sender udp help
+
+               Show the access control list and the number of connected clients of
+               the http sender:
+
+                       sender http status
+
+               Senders may be activated and deactivated independently of each
+               other. The following command switches off the dccp sender:
+
+                       sender dccp off
+
+               Add an UDP unicast for a client to the target list of the UDP sender:
+
+                       sender udp add client.foo.org
+
+               Start UDP multicast, using the default multicast address:
+
+                       sender udp add 224.0.1.38
+
+       [/description]
+
+[subcommand setatt]
+       purpose = set or unset attributes
+       synopsis = attribute{+|-}... pattern...
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               Set ('+') or unset ('-') the given attributes for all audio files
+               matching the given pattern. Example:
+
+                       setatt rock+ punk+ pop- '*foo.mp3'
+
+               sets the 'rock' and the 'punk' attribute and unsets the 'pop' attribute
+               of all files ending with 'foo.mp3'.
+       [/description]
+
+[subcommand si]
+       purpose = print server info
+       aux_info = NO_PERMISSION_REQUIRED
+       [description]
+               Show server and afs PID, number of connections, uptime and more.
+       [/description]
+
+[subcommand stat]
+       purpose = print information about the current audio file
+       aux_info = VSS_READ
+       [option num]
+               short_opt = n
+               summary = number of times to show the status info
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = num
+               [help]
+                       Exit after the status information has been shown num times. If this
+                       option is not given, the command runs in an endless loop.
+               [/help]
+       [option parser-friendly]
+               short_opt = p
+               summary = enable parser-friendly output
+               [help]
+                       Show status item identifiers as numerical values and prefix each
+                       status item with its size in bytes.
+               [/help]
+
+[subcommand stop]
+       purpose = stop playback
+       aux_info = VSS_READ | VSS_WRITE
+       [description]
+               Clear the 'P' (playing) bit and set the 'N' (next audio file) bit of
+               the vss status flags, effectively stopping playback.
+       [/description]
+
+[subcommand tasks]
+       purpose = list active server tasks (deprecated)
+       aux_info = NO_PERMISSION_REQUIRED
+       [description]
+               This used to print the ID, the status and the name of each task,
+               mainly for debugging purposes. As of version 0.6.2, the subcommand
+               prints nothing. It will be removed in 0.7.0. Don't use.
+       [/description]
+
+[subcommand term]
+       purpose = ask the server to terminate
+       aux_info = VSS_READ | VSS_WRITE
+       [description]
+               Shut down the server. Instead of this command, you can also send
+               SIGINT or SIGTERM to the para_server process. It should never be
+               necessary to send SIGKILL.
+       [/description]
+
+[subcommand touch]
+       purpose = manipulate the afs information of audio files
+       non-opts-name = pattern...
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               This command modifies the afs info structure of all rows of the audio
+               file table whose path matches at least one of the given patters.
+
+               If at least one option is given which takes a number as its argument,
+               only those fields of the afs info structure are updated which
+               correspond to the given options while all other fields stay unmodified.
+
+               If no such option is given, the lastplayed field is set to the current
+               time and the value of the numplayed field is increased by one while
+               all other fields are left unchanged. This mimics what happens when
+               the virtual streaming system selects the file for streaming.
+
+               If the file is admissible for the current mood (or contained in the
+               current playlist), its score is recomputed according to the changed
+               values.
+       [/description]
+       [option numplayed]
+               short_opt = n
+               summary = set the numplayed count manually
+               arg_type = uint32
+               arg_info = required_arg
+               typestr = num
+               [help]
+                       The numplayed count of an audio file is the number of times the file
+                       was selected for streaming. It is one of the inputs to the scoring
+                       function which determines the order in which admissible files are
+                       streamed.
+
+                       The virtual streaming system increases this number automatically each
+                       time it opens the file for streaming.
+               [/help]
+       [option lastplayed]
+               short_opt = l
+               summary = set the lastplayed time manually
+               arg_type = uint64
+               arg_info = required_arg
+               typestr = num
+               [help]
+                       The lastplayed time of an audio file is the time when the file was
+                       last opened for streaming.
+
+                       Like the numplayed count, it is an input for the scoring function
+                       and is updated automatically by the virtual streaming system.
+
+                       The argument must be a number of seconds since the epoch. Example:
+
+                               touch -l=$(date +%s) file
+
+                       sets the lastplayed time of 'file' to the current time.
+               [/help]
+       [option image-id]
+               short_opt = i
+               summary = set the image id
+               arg_type = uint32
+               arg_info = required_arg
+               typestr = num
+               [help]
+                       The afs info structure of each row of the audio file table contains
+                       a slot for the image id of the audio file that corresponds to the
+                       row. The image id stored in this slot refers to the key in the image
+                       table that identifies the blob.
+
+                       When a new audio file is added to the audio file table, its image
+                       id starts out as zero, indicating that there is no image associated
+                       with the file. Setting the image id to a non-zero number associates
+                       the file with a particular blob of the image table, for example the
+                       cover art of the album in jpg format.
+               [/help]
+       [option lyrics-id]
+               short_opt = y
+               summary = set the lyrics id
+               arg_type = uint32
+               arg_info = required_arg
+               typestr = num
+               [help]
+                       This option works just like --image-id, but sets the lyrics ID rather
+                       than the image id.
+               [/help]
+       [option amp]
+               short_opt = a
+               summary = set the amplification value (0-255)
+               arg_type = uint32
+               arg_info = required_arg
+               typestr = num
+               [help]
+                       The amplification value of an audio file is a number which is stored
+                       in the afs info structure.
+
+                       The value determines the scaling factor by which the amplitude of
+                       the decoded samples should be multiplied in order to normalize the
+                       volume. A value of zero means no amplification, 64 means the amplitude
+                       should be multiplied by a factor of two, 128 by three and so on.
+
+                       The amp filter of para_audiod amplifies the volume according to
+                       this value.
+               [/help]
+       [option verbose]
+               short_opt = v
+               summary = explain what is being done
+       [option pathname-match]
+               short_opt = p
+               summary = modify matching behaviour
+               [help]
+                       Match a slash in the path only with a slash in pattern and not by an
+                       asterisk (*) or a question mark (?) metacharacter, nor by a bracket
+                       expression ([]) containing a slash (see fnmatch(3)).
+               [/help]
+
+[subcommand version]
+       purpose = print the git version string of para_server
+       aux_info = NO_PERMISSION_REQUIRED
+       [option verbose]
+               short_opt = v
+               summary = print detailed (multi-line) version text
+
+m4_define(`BLOB_COMMANDS', `
+[subcommand rm`$2']
+       purpose = remove `$1' blob(s)
+       non-opts-name = pattern...
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               Remove all `$1' blobs which match any of the given patterns.
+       [/description]
+
+[subcommand mv`$2']
+       purpose = rename `$1' blob(s)
+       non-opts-name = source dest
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               Rename `$1' source to dest. The command fails if the source `$1'
+               does not exist or if the destination `$1' already exists.
+       [/description]
+
+[subcommand add`$2']
+       purpose = add a blob to the `$1' table
+       non-opts-name = `$1'_name
+       aux_info = AFS_READ | AFS_WRITE
+       [description]
+               Read from stdin and ask the audio file selector to create a blob in
+               the `$1' table. If the named blob already exists, it gets replaced
+               with the new data.
+       [/description]
+
+[subcommand cat`$2']
+       purpose = dump a `$1' blob to stdout
+       non-opts-name = `$1'_name
+       aux_info = AFS_READ
+
+[subcommand ls`$2']
+       purpose = list blobs of type `$1' which match a pattern
+       non-opts-name = [pattern...]
+       aux_info = AFS_READ
+       [description]
+               Print the list of all blobs which match the given pattern. If no
+               pattern is given, the full list is printed.
+       [/description]
+       [option id-sort]
+               short_opt = i
+               summary = sort by identifier
+               [help]
+                       The default is to sort alphabetically by name.
+               [/help]
+       [option long]
+               short_opt = l
+               summary = long listing
+               [help]
+                       Print identifier and name. The default is to print only the name.
+               [/help]
+       [option reverse]
+               short_opt = r
+               summary = reverse sort order
+')
+
+BLOB_COMMANDS(`moods', `mood')
+BLOB_COMMANDS(`playlist', `pl')
+BLOB_COMMANDS(`image', `img')
+BLOB_COMMANDS(`lyrics', `lyr')
diff --git a/m4/lls/write.suite.m4 b/m4/lls/write.suite.m4
new file mode 100644 (file)
index 0000000..a2f50df
--- /dev/null
@@ -0,0 +1,35 @@
+m4_define(PROGRAM, para_write)
+[suite write]
+version-string = GIT_VERSION()
+[supercommand para_write]
+       purpose = play wav or raw audio
+       [description]
+               para_write reads audio data from stdin and starts one supported writer.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       m4_include(per-command-options-section.m4)
+       [option writer]
+               short_opt = w
+               summary = select writer to start
+               arg_info = required_arg
+               arg_type = string
+               flag multiple
+               typestr = 'name [options]'
+               [help]
+                       May be given multiple times, and the same writer may be specified more
+                       than once. If this option is not given, the first supported writer
+                       is started. The list of supported writers is shown in the help output.
+
+                       Options for a particular writer may be specified for each given
+                       '--writer' option separately. You will have to quote these options
+                       like this:
+
+                               --writer 'oss --device /dev/dsp2'
+               [/help]
+       m4_include(channels.m4)
+       m4_include(sample-rate.m4)
+       m4_include(sample-format.m4)
diff --git a/m4/lls/write_cmd.suite.m4 b/m4/lls/write_cmd.suite.m4
new file mode 100644 (file)
index 0000000..102afed
--- /dev/null
@@ -0,0 +1,76 @@
+[suite write_cmd]
+caption = writers
+[subcommand alsa]
+       purpose = native ALSA output plugin
+       [option device]
+               short_opt = d
+               summary = set PCM device
+               typestr = device
+               arg_info = required_arg
+               arg_type = string
+               default_val = default
+               [help]
+                       Check for the presence of a /proc/asound/ directory to see if ALSA
+                       is present in your kernel. The file /proc/asound/devices contains
+                       all devices ALSA knows about.
+               [/help]
+       [option buffer-time]
+               short_opt = B
+               summary = duration of the ALSA buffer
+               typestr = milliseconds
+               arg_info = required_arg
+               arg_type = uint32
+               default_val = 170
+               [help]
+                       This is only a hint as ALSA might pick a slightly different time,
+                       depending on the sound hardware. The chosen value is shown in debug
+                       output as BUFFER_TIME.
+
+                       If synchronization between multiple clients is desired, the same
+                       buffer time should be configured for all clients.
+               [/help]
+[subcommand ao]
+       purpose = output plugin for libao
+       [option driver]
+               short_opt = d
+               summary = select a output driver by name
+               typestr = name
+               arg_info = required_arg
+               arg_type = string
+               [help]
+                       If this is not given, the driver with the highest priority will be
+                       used. The list of available drivers and their priorities is shown in
+                       debug mode.
+               [/help]
+       [option ao-option]
+               short_opt = o
+               summary = pass a key-value pair to the libao driver
+               typestr = key:value
+               arg_info = required_arg
+               arg_type = string
+               flag multiple
+               [help]
+                       For each time this option is given, the supplied key-value pair is
+                       appended to the list of options for the driver. Invalid keys are
+                       silently ignored. The list of available keys is shown in debug mode.
+               [/help]
+[subcommand oss]
+       purpose = output plugin for the Open Sound System
+       [option device]
+               short_opt = d
+               summary = set PCM device
+               typestr = path
+               arg_info = required_arg
+               arg_type = string
+               default_val = /dev/dsp
+[subcommand file]
+       purpose = output plugin that writes to a local file
+       [option filename]
+               short_opt = f
+               summary = specify output file name
+               typestr = path
+               arg_info = required_arg
+               arg_type = string
+               [help]
+                       Defaults to a random filename in ~/.paraslash.
+               [/help]
diff --git a/man_util.bash b/man_util.bash
deleted file mode 100755 (executable)
index cb7519c..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/usr/bin/env bash
-
-# Receivers, filters, writers are called "modules" in this script
-print_modhelp()
-{
-       local ggo="$1"
-
-       $GENGETOPT --show-detailed-help \
-               --set-version "" \
-               --set-package "" \
-               < "$ggo" | awk 'BEGIN {
-                       have_purpose=0
-                       have_usage=0
-               } {
-                       if (!have_purpose) {
-                               if ($0 ~ /^ *$/)
-                                       next
-                               printf(" (%s):", $0)
-                               have_purpose=1
-                               next
-                       }
-                       if (!have_usage) {
-                               if ($0 ~ /^Usage: /) {
-                                       have_usage=1
-                               }
-                               next
-                       }
-                       print $0
-               }'
-}
-
-make_help()
-{
-       local target="$1" module ggo
-
-       ggo="$GGO_DIR/$1.ggo"
-       $GENGETOPT --show-detailed-help \
-               --set-version "$VERSION" \
-               --set-package "para_$1" \
-               < "$ggo"
-
-       if [[ "$target" == 'recv' || "$target" == 'audiod' ]]; then
-               for module in $RECEIVERS; do
-                       ggo="$GGO_DIR/${module}_recv.ggo"
-                       [[ ! -f "$ggo" ]] && continue
-                       printf "\nOptions for the $module receiver"
-                       print_modhelp "$ggo"
-               done
-       fi
-       if [[ "$target" == 'filter' || "$target" == 'audiod' ]]; then
-               for module in $FILTERS; do
-                       ggo="$GGO_DIR/${module}_filter.ggo"
-                       [[ ! -f "$ggo" ]] && continue
-                       printf "\nOptions for the $module filter"
-                       print_modhelp "$ggo"
-               done
-       fi
-       if [[ "$target" == 'write' || "$target" == 'audiod' ]]; then
-               for module in $WRITERS; do
-                       ggo="$GGO_DIR/${module}_write.ggo"
-                       [[ ! -f "$ggo" ]] && continue
-                       printf "\nOptions for the $module writer"
-                       print_modhelp "$ggo"
-               done
-       fi
-}
-
-set -u
-
-(($# != 1)) && exit 1
-
-# These must be set by the caller (make or help2man)
-export COMMAND_LISTS FILTERS GENGETOPT GGO_DIR HELP2MAN HELP2MAN_DIR \
-       RECEIVERS VERSION WRITERS
-
-# If either --version or --help-xxx was given, we are being called by help2man
-if [[ "$1" == "--version" ]]; then
-       echo "$VERSION"
-       exit $?
-fi
-if [[ "$1" =~ --help- ]]; then
-       make_help "${1#--help-}"
-       exit $?
-fi
-
-# Called by make, run help2man
-output_file="$1"
-target="${output_file##*/para_}"
-target="${target%.*}" # server, audiod, filter, ...
-link="$HELP2MAN_DIR/para_$target"
-
-cl_opts=
-for cl in $COMMAND_LISTS; do
-       cl_opts+=" --include $cl"
-done
-
-# Create a symlink para_$target, pointing to this script. This hack is
-# necessary because help2man always includes the name of the executable in its
-# output.
-ln -sf "$PWD/$0" "$link"
-
-# This will call us again twice, with either --help-$target or --version given.
-$HELP2MAN --no-info --help-option "--help-$target" $cl_opts \
-       "$link" > "$output_file"
-if (($? != 0)); then
-       rm -f "$output_file"
-       exit 1
-fi
diff --git a/mix.h b/mix.h
index 60c4392fabab76bb58ad1c2ee0ee211d52469ad7..cad435e811e09a9d3d924f946608fd57cce81ed1 100644 (file)
--- a/mix.h
+++ b/mix.h
@@ -1,10 +1,6 @@
-/*
- * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
-/** \file mix.h Mixer API (used by para_fade). */
+/** \file mix.h Mixer API for para_mixer. */
 
 /**
  * Opaque structure which corresponds to an instance of a mixer.
@@ -16,10 +12,14 @@ struct mixer_handle;
 
 /**
  * Operations provided by each mixer plugin.
+ *
+ * Each mixer plugin must define a non-static instance of this structure, with
+ * all pointers initialized to non-NULL values. No other symbols need to be
+ * exported.
  */
 struct mixer {
-       /** Called on startup, must fill in all other members. */
-       void (*init)(struct mixer *self);
+       /** Used to identify the mixer. */
+       const char * const name;
        /** Return a handle that can be passed to other methods. */
        int (*open)(const char *dev, struct mixer_handle **handle);
        /** Returns a string of all valid mixer channels. */
@@ -34,3 +34,6 @@ struct mixer {
        /** Free all resources associated with the given handle. */
        void (*close)(struct mixer_handle **handle);
 };
+
+/** Declared even if unsupported because it does not hurt and avoids ifdefs. */
+extern const struct mixer alsa_mixer, oss_mixer;
diff --git a/mixer.c b/mixer.c
new file mode 100644 (file)
index 0000000..eae8929
--- /dev/null
+++ b/mixer.c
@@ -0,0 +1,586 @@
+/* Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file mixer.c A volume fader and alarm clock. */
+
+#include <regex.h>
+#include <lopsub.h>
+#include <math.h>
+
+#include "mixer.lsg.h"
+#include "para.h"
+#include "lsu.h"
+#include "fd.h"
+#include "string.h"
+#include "mix.h"
+#include "error.h"
+#include "version.h"
+
+/** Array of error strings. */
+DEFINE_PARA_ERRLIST;
+
+/* At least one of the two is defined if this file gets compiled. */
+static const struct mixer *mixers[] = {
+#ifdef HAVE_ALSA
+       &alsa_mixer,
+#endif
+#ifdef HAVE_OSS
+       &oss_mixer,
+#endif
+};
+
+#define NUM_SUPPORTED_MIXERS (ARRAY_SIZE(mixers))
+#define FOR_EACH_MIXER(i) for ((i) = 0; (i) < NUM_SUPPORTED_MIXERS; (i)++)
+
+static struct lls_parse_result *lpr, *sub_lpr;
+
+#define CMD_PTR(_cmd) (lls_cmd(LSG_MIXER_CMD_ ## _cmd, mixer_suite))
+#define OPT_RESULT(_cmd, _opt) (lls_opt_result( \
+       LSG_MIXER_ ## _cmd ## _OPT_ ## _opt, (LSG_MIXER_CMD_ ## _cmd == 0)? lpr : sub_lpr))
+#define OPT_GIVEN(_cmd, _opt) (lls_opt_given(OPT_RESULT(_cmd, _opt)))
+#define OPT_STRING_VAL(_cmd, _opt) (lls_string_val(0, OPT_RESULT(_cmd, _opt)))
+#define OPT_UINT32_VAL(_cmd, _opt) (lls_uint32_val(0, OPT_RESULT(_cmd, _opt)))
+
+typedef int (*mixer_subcommand_handler_t)(const struct mixer *);
+
+#define EXPORT_CMD(_cmd) const mixer_subcommand_handler_t \
+       lsg_mixer_com_ ## _cmd ## _user_data = &com_ ## _cmd;
+
+static int loglevel;
+static __printf_2_3 void date_log(int ll, const char *fmt, ...)
+{
+       va_list argp;
+       time_t t1;
+       struct tm *tm;
+
+       if (ll < loglevel)
+               return;
+       time(&t1);
+       tm = localtime(&t1);
+       fprintf(stderr, "%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec);
+       va_start(argp, fmt);
+       vprintf(fmt, argp);
+       va_end(argp);
+}
+__printf_2_3 void (*para_log)(int, const char*, ...) = date_log;
+
+static int set_channel(const struct mixer *m, struct mixer_handle *h,
+               const char *channel)
+{
+
+       PARA_NOTICE_LOG("using %s mixer channel\n", channel?
+               channel : "default");
+       return m->set_channel(h, channel);
+}
+
+static void millisleep(int ms)
+{
+       struct timespec ts;
+
+       PARA_INFO_LOG("sleeping %dms\n", ms);
+       if (ms < 0)
+               return;
+       ts.tv_sec = ms / 1000,
+       ts.tv_nsec = (ms % 1000) * 1000 * 1000;
+       nanosleep(&ts, NULL);
+}
+
+/*
+ * This implements the inverse function of t -> t^alpha, scaled to the time
+ * interval [0,T] and the range given by old_vol and new_vol. It returns the
+ * amount of milliseconds until the given volume is reached.
+ */
+static unsigned volume_time(double vol, double old_vol, double new_vol,
+               double T, double alpha)
+{
+       double c, d, x;
+
+       if (old_vol < new_vol) {
+               c = old_vol;
+               d = new_vol;
+       } else {
+               c = new_vol;
+               d = old_vol;
+       }
+
+       x = T * exp(log(((vol - c) / (d - c))) / alpha);
+       assert(x <= T);
+       if (old_vol < new_vol)
+               return x;
+       else
+               return T - x;
+}
+
+/* Fade to new volume in fade_time seconds. */
+static int fade(const struct mixer *m, struct mixer_handle *h, uint32_t new_vol,
+               uint32_t fade_time)
+{
+       int i, T, old_vol, ret, slept, incr;
+       double ms, alpha;
+       uint32_t fe = OPT_UINT32_VAL(PARA_MIXER, FADE_EXPONENT);
+
+       if (fade_time <= 0 || fe >= 100) {
+               ret = m->set(h, new_vol);
+               if (ret < 0)
+                       return ret;
+               goto sleep;
+       }
+       alpha = (100 - fe) / 100.0;
+       ret = m->get(h);
+       if (ret < 0)
+               return ret;
+       old_vol = ret;
+       if (old_vol == new_vol)
+               goto sleep;
+       PARA_NOTICE_LOG("fading %s from %d to %u in %u seconds\n",
+               OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL), old_vol,
+               new_vol, fade_time);
+       incr = old_vol < new_vol? 1 : -1;
+       T = fade_time * 1000;
+       i = old_vol;
+       slept = 0;
+       do {
+               ms = volume_time(i + incr, old_vol, new_vol, T, alpha);
+               millisleep(ms - slept);
+               slept = ms;
+               i += incr;
+               ret = m->set(h, i);
+               if (ret < 0)
+                       return ret;
+       } while (i != new_vol);
+       return 1;
+sleep:
+       sleep(fade_time);
+       return ret;
+}
+
+static int open_mixer_and_set_channel(const struct mixer *m, struct mixer_handle **h)
+{
+       int ret;
+
+       ret = m->open(OPT_STRING_VAL(PARA_MIXER, MIXER_DEVICE), h);
+       if (ret < 0)
+               return ret;
+       ret = set_channel(m, *h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
+       if (ret == -E_BAD_CHANNEL) {
+               char *channels = m->get_channels(*h);
+               printf("Available channels: %s\n", channels);
+               free(channels);
+       }
+       if (ret < 0)
+               m->close(h);
+       return ret;
+}
+
+static int com_fade(const struct mixer *m)
+{
+       uint32_t new_vol = OPT_UINT32_VAL(FADE, FADE_VOL);
+       uint32_t fade_time = OPT_UINT32_VAL(FADE, FADE_TIME);
+       struct mixer_handle *h;
+       int ret;
+
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
+       ret = fade(m, h, new_vol, fade_time);
+       m->close(&h);
+       return ret;
+}
+EXPORT_CMD(fade);
+
+static void client_cmd(const char *cmd)
+{
+       int ret, status, fds[3] = {0, 0, 0};
+       pid_t pid;
+       char *cmdline = make_message(BINDIR "/para_client %s", cmd);
+
+       PARA_NOTICE_LOG("%s\n", cmdline);
+       ret = para_exec_cmdline_pid(&pid, cmdline, fds);
+       free(cmdline);
+       if (ret < 0) {
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               goto fail;
+       }
+       do
+               pid = waitpid(pid, &status, 0);
+       while (pid == -1 && errno == EINTR);
+       if (pid < 0) {
+               PARA_ERROR_LOG("%s\n", strerror(errno));
+               goto fail;
+       }
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+               goto fail;
+       return;
+fail:
+       PARA_EMERG_LOG("command \"%s\" failed\n", cmd);
+       exit(EXIT_FAILURE);
+}
+
+static void change_afs_mode(const char *afs_mode)
+{
+       char *cmd;
+
+       cmd = make_message("select %s", afs_mode);
+       client_cmd(cmd);
+       free(cmd);
+}
+
+static int set_initial_volume(const struct mixer *m, struct mixer_handle *h)
+{
+       int i, ret;
+
+       for (i = 0; i < OPT_GIVEN(SLEEP, IVOL); i++) {
+               const char *val = lls_string_val(i, OPT_RESULT(SLEEP, IVOL));
+               char *p, *ch, *arg = para_strdup(val);
+               int32_t iv;
+               p = strchr(arg, ':');
+               if (p) {
+                       *p = '\0';
+                       p++;
+                       ch = arg;
+               } else {
+                       p = arg;
+                       ch = NULL;
+               }
+               ret = para_atoi32(p, &iv);
+               if (ret < 0) {
+                       free(arg);
+                       return ret;
+               }
+               ret = set_channel(m, h, ch);
+               if (!ch)
+                       ch = "default";
+               if (ret < 0) {
+                       PARA_WARNING_LOG("ignoring channel %s\n", ch);
+                       ret = 0;
+               } else {
+                       PARA_INFO_LOG("initial volume %s: %d\n", ch, iv);
+                       ret = m->set(h, iv);
+               }
+               free(arg);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+static int com_sleep(const struct mixer *m)
+{
+       time_t t1, wake_time_epoch;
+       unsigned int delay;
+       struct tm *tm;
+       int ret;
+       const char *wake_time = OPT_STRING_VAL(SLEEP, WAKE_TIME);
+       const char *fo_mood = OPT_STRING_VAL(SLEEP, FO_MOOD);
+       const char *fi_mood = OPT_STRING_VAL(SLEEP, FI_MOOD);
+       const char *sleep_mood = OPT_STRING_VAL(SLEEP, SLEEP_MOOD);
+       int fit = OPT_UINT32_VAL(SLEEP, FI_TIME);
+       int fot = OPT_UINT32_VAL(SLEEP, FO_TIME);
+       int fiv = OPT_UINT32_VAL(SLEEP, FI_VOL);
+       int fov = OPT_UINT32_VAL(SLEEP, FO_VOL);
+       int32_t hour, min = 0;
+       char *tmp, *wt;
+       struct mixer_handle *h;
+
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
+       wt = para_strdup(wake_time + (wake_time[0] == '+'));
+       /* calculate wake time */
+       time(&t1);
+       tmp = strchr(wt, ':');
+       if (tmp) {
+               *tmp = '\0';
+               tmp++;
+               ret = para_atoi32(tmp, &min);
+               if (ret < 0) {
+                       free(wt);
+                       goto close_mixer;
+               }
+       }
+       ret = para_atoi32(wt, &hour);
+       free(wt);
+       if (ret < 0)
+               goto close_mixer;
+       if (wake_time[0] == '+') { /* relative */
+               t1 += hour * 60 * 60 + min * 60;
+               tm = localtime(&t1);
+       } else {
+               tm = localtime(&t1);
+               if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) {
+                       t1 += 86400; /* wake time is tomorrow */
+                       tm = localtime(&t1);
+               }
+               tm->tm_hour = hour;
+               tm->tm_min = min;
+               tm->tm_sec = 0;
+       }
+       wake_time_epoch = mktime(tm);
+       PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min);
+       client_cmd("stop");
+       sleep(1);
+       if (fot && fo_mood && *fo_mood) {
+               ret = set_initial_volume(m, h);
+               if (ret < 0)
+                       goto close_mixer;
+               change_afs_mode(fo_mood);
+               client_cmd("play");
+               ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
+               if (ret < 0)
+                       goto close_mixer;
+               ret = fade(m, h, fov, fot);
+               if (ret < 0)
+                       goto close_mixer;
+       } else {
+               ret = m->set(h, fov);
+               if (ret < 0)
+                       goto close_mixer;
+       }
+       if (sleep_mood && *sleep_mood) {
+               change_afs_mode(sleep_mood);
+               if (!fot || !fo_mood) /* currently stopped */
+                       client_cmd("play");
+       } else if (fot && fo_mood && *fo_mood) /* currently playing */
+               client_cmd("stop");
+       m->close(&h);
+       if (!fit || !fi_mood || !*fi_mood) /* nothing to do */
+               return 1;
+       for (;;) {
+               time(&t1);
+               if (wake_time_epoch <= t1 + fit)
+                       break;
+               delay = wake_time_epoch - t1 - fit;
+               PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n",
+                       delay, delay / 3600,
+                       (delay % 3600) / 60);
+               sleep(delay);
+       }
+       change_afs_mode(fi_mood);
+       if (sleep_mood && *sleep_mood) /* currently playing */
+               client_cmd("next");
+       else /* currently stopped */
+               client_cmd("play");
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
+       ret = fade(m, h, fiv, fit);
+close_mixer:
+       m->close(&h);
+       return ret;
+}
+EXPORT_CMD(sleep);
+
+static int com_snooze(const struct mixer *m)
+{
+       int ret, val;
+       struct mixer_handle *h;
+
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
+       ret = 1;
+       if (OPT_UINT32_VAL(SNOOZE, SO_TIME) == 0)
+               goto close_mixer;
+       ret = m->get(h);
+       if (ret < 0)
+               goto close_mixer;
+       val = ret;
+       if (val < OPT_UINT32_VAL(SNOOZE, SO_VOL))
+               ret = m->set(h, OPT_UINT32_VAL(SNOOZE, SO_VOL));
+       else
+               ret = fade(m, h, OPT_UINT32_VAL(SNOOZE, SO_VOL),
+                       OPT_UINT32_VAL(SNOOZE, SO_TIME));
+       if (ret < 0)
+               goto close_mixer;
+       client_cmd("pause");
+       PARA_NOTICE_LOG("%" PRIu32 " seconds snooze time...\n",
+               OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME));
+       m->close(&h);
+       sleep(OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME));
+       client_cmd("play");
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               goto close_mixer;
+       ret = fade(m, h, OPT_UINT32_VAL(SNOOZE, SI_VOL),
+               OPT_UINT32_VAL(SNOOZE, SI_TIME));
+close_mixer:
+       m->close(&h);
+       return ret;
+}
+EXPORT_CMD(snooze);
+
+static int com_set(const struct mixer *m)
+{
+       struct mixer_handle *h;
+       int ret;
+
+       ret = open_mixer_and_set_channel(m, &h);
+       if (ret < 0)
+               return ret;
+       ret = m->set(h, OPT_UINT32_VAL(SET, VAL));
+       m->close(&h);
+       return ret;
+}
+EXPORT_CMD(set);
+
+static const struct mixer *get_mixer_or_die(void)
+{
+       int i;
+
+       if (!OPT_GIVEN(PARA_MIXER, MIXER_API))
+               i = 0; /* default: use first mixer */
+       else
+               FOR_EACH_MIXER(i)
+                       if (!strcmp(mixers[i]->name,
+                                       OPT_STRING_VAL(PARA_MIXER, MIXER_API)))
+                               break;
+       if (i < NUM_SUPPORTED_MIXERS) {
+               PARA_NOTICE_LOG("using %s mixer API\n", mixers[i]->name);
+               return mixers[i];
+       }
+       printf("available mixer APIs: ");
+       FOR_EACH_MIXER(i)
+               printf("%s%s%s ", i == 0? "[" : "", mixers[i]->name,
+                       i == 0? "]" : "");
+       printf("\n");
+       exit(EXIT_FAILURE);
+}
+
+static void show_subcommands(void)
+{
+       const struct lls_command *cmd;
+       int i;
+       printf("Subcommands:\n");
+       for (i = 1; (cmd = lls_cmd(i, mixer_suite)); i++) {
+               const char *name = lls_command_name(cmd);
+               const char *purpose = lls_purpose(cmd);
+               printf("%-20s%s\n", name, purpose);
+       }
+}
+
+static int com_help(__a_unused const struct mixer *m)
+{
+       const struct lls_command *cmd;
+       const struct lls_opt_result *r_l = OPT_RESULT(HELP, LONG);
+       char *txt, *errctx;
+       const char *name;
+       int ret;
+
+       ret = lls_check_arg_count(sub_lpr, 0, 1, NULL);
+       if (ret < 0)
+               return ret;
+       if (lls_num_inputs(sub_lpr) == 0) {
+               show_subcommands();
+               return 0;
+       }
+       name = lls_input(0, sub_lpr);
+       ret = lls(lls_lookup_subcmd(name, mixer_suite, &errctx));
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               return ret;
+       }
+       cmd = lls_cmd(ret, mixer_suite);
+       if (lls_opt_given(r_l))
+               txt = lls_long_help(cmd);
+       else
+               txt = lls_short_help(cmd);
+       printf("%s", txt);
+       free(txt);
+       return 0;
+}
+EXPORT_CMD(help);
+
+static void handle_help_flags(void)
+{
+       char *help;
+
+       if (OPT_GIVEN(PARA_MIXER, DETAILED_HELP))
+               help = lls_long_help(CMD_PTR(PARA_MIXER));
+       else if (OPT_GIVEN(PARA_MIXER, HELP))
+               help = lls_short_help(CMD_PTR(PARA_MIXER));
+       else
+               return;
+       printf("%s", help);
+       free(help);
+       show_subcommands();
+       exit(EXIT_SUCCESS);
+}
+
+static int parse_and_merge_config_file(const struct lls_command *cmd)
+{
+       int ret;
+       struct lls_parse_result **lprp = (cmd == lls_cmd(0, mixer_suite))?
+               &lpr : &sub_lpr;
+
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(PARA_MIXER,
+               CONFIG_FILE), "mixer.conf", lprp, cmd, mixer_suite,
+               0 /* flags */);
+       if (ret < 0)
+               return ret;
+       loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
+       return 1;
+}
+
+/**
+ * The main function of para_mixer.
+ *
+ * The executable is linked with the alsa or the oss mixer API, or both. It has
+ * a custom log function which prefixes log messages with the current date.
+ *
+ * \param argc Argument counter.
+ * \param argv Argument vector.
+ *
+ * \return EXIT_SUCCESS or EXIT_FAILURE.
+ */
+int main(int argc, char *argv[])
+{
+       const struct lls_command *cmd = CMD_PTR(PARA_MIXER);
+       int ret;
+       char *errctx;
+       const char *subcmd;
+       const struct mixer *m;
+       unsigned n;
+
+       ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
+       if (ret < 0)
+               goto fail;
+       loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
+       version_handle_flag("mixer", OPT_GIVEN(PARA_MIXER, VERSION));
+       handle_help_flags();
+
+       n = lls_num_inputs(lpr);
+       if (n == 0) {
+               show_subcommands();
+               ret = 0;
+               goto free_lpr;
+       }
+       ret = parse_and_merge_config_file(cmd);
+       if (ret < 0)
+               goto free_lpr;
+       subcmd = lls_input(0, lpr);
+       ret = lls(lls_lookup_subcmd(subcmd, mixer_suite, &errctx));
+       if (ret < 0)
+               goto fail;
+       cmd = lls_cmd(ret, mixer_suite);
+       ret = lls(lls_parse(n, argv + argc - n, cmd, &sub_lpr, &errctx));
+       if (ret < 0)
+               goto free_lpr;
+       ret = parse_and_merge_config_file(cmd);
+       if (ret < 0)
+               goto free_sub_lpr;
+       m = get_mixer_or_die();
+       ret = (*(mixer_subcommand_handler_t *)(lls_user_data(cmd)))(m);
+free_sub_lpr:
+       lls_free_parse_result(sub_lpr, cmd);
+free_lpr:
+       lls_free_parse_result(lpr, CMD_PTR(PARA_MIXER));
+       if (ret >= 0)
+               return EXIT_SUCCESS;
+fail:
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", errctx);
+       free(errctx);
+       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       return EXIT_FAILURE;
+}
diff --git a/mm.c b/mm.c
index 92856ec3ef903eb905b0379c6eaff605bf6f8d06..358783a193a2366fdf402506ffe3eb91a699b0d8 100644 (file)
--- a/mm.c
+++ b/mm.c
@@ -1,14 +1,11 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file mm.c Paraslash's mood methods. */
 
 #include <regex.h>
 #include <fnmatch.h>
 #include <osl.h>
+#include <lopsub.h>
 
 #include "para.h"
 #include "error.h"
diff --git a/mm.h b/mm.h
index 2caace7f84220c23119ccb7d1bd7140212086a6a..28038e729f9bc4ac299aaef04b39546c3b5e3ccd 100644 (file)
--- a/mm.h
+++ b/mm.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file mm.h Symbols and declarations for mood methods. */
 
@@ -17,7 +13,7 @@
  * Mood score functions must return values between -100 and +100 inclusively.
  * Boolean score functions should always return either -100 or +100.
  *
- * \sa struct mood_method, mood_parser.
+ * \sa struct \ref mood_method, \ref mood_parser.
  */
 typedef int mood_score_function(const char *path, const struct afs_info *afsi,
                const struct afh_info *afhi, const void *data);
@@ -36,19 +32,15 @@ typedef int mood_score_function(const char *path, const struct afs_info *afsi,
  * later in the mood_score_function of the mood_method. The mood_parser may
  * store a pointer to its structure via the void** pointer.
  *
- * \sa mood_open(), mood_cleanup_function, mood_score_function.
+ * \sa \ref mood_cleanup_function, \ref mood_score_function.
  */
 typedef int mood_parser(int, char **, void **);
 
 /**
  * Deallocate resources which were allocated by the mood_parser.
  *
- * This optional function of a mood_method is used to free any resources
- * allocated in mood_open() by the mood_parser. The argument passed is a
- * pointer to the mood_method specific data structure that was returned by the
- * mood_parser.
- *
- * \sa mood_parser.
+ * Function to free the resources allocated in \ref mood_method::parser. The
+ * argument is a pointer to mood method specific data returned by ->parser().
  */
 typedef void mood_cleanup_function(void *);
 
diff --git a/mood.c b/mood.c
index 196d80e2882756214da91f3ad663258debf68ef2..a63d4d2af5d10d7b64c319d915e00b9b7ea62e89 100644 (file)
--- a/mood.c
+++ b/mood.c
@@ -1,13 +1,10 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file mood.c Paraslash's mood handling functions. */
 
 #include <regex.h>
 #include <osl.h>
+#include <lopsub.h>
 
 #include "para.h"
 #include "error.h"
 #include "afh.h"
 #include "afs.h"
 #include "list.h"
-#include "ipc.h"
 #include "mm.h"
-#include "sideband.h"
 #include "mood.h"
-#include "sched.h"
+
+/*
+ * Mood parser API. It's overkill to have an own header file for
+ * these declarations as they are only needed in this .c file.
+ */
+struct mp_context;
+int mp_init(const char *definition, int nbytes, struct mp_context **result,
+                char **errmsg);
+bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx);
+void mp_shutdown(struct mp_context *ctx);
 
 /**
  * Contains statistical data of the currently admissible audio files.
@@ -58,15 +62,13 @@ struct mood_item {
        struct list_head mood_item_node;
 };
 
-/**
- * Created from the mood definition by mood_open().
+/*
+ * Created from the mood definition by \ref change_current_mood().
  *
  * When a mood is opened, each line of its definition is investigated, and a
- * corresponding mood item is produced. Each mood line starts with \p accept,
- * \p deny, or \p score which determines the type of the mood line.  For each
- * such type a linked list is maintained whose entries are the mood items.
- *
- * \sa mood_item, mood_open().
+ * corresponding mood item is produced. Each mood line starts with accept,
+ * deny, or score which determines the type of the mood line. For each such
+ * type a linked list is maintained whose entries are the mood items.
  */
 struct mood {
        /** The name of this mood. */
@@ -77,40 +79,70 @@ struct mood {
        struct list_head deny_list;
        /** The list of mood items of type \p score. */
        struct list_head score_list;
+       /* Only used for version 2 moods. */
+       struct mp_context *parser_context;
 };
 
 /*
  * If current_mood is NULL then no mood is currently open. If
- * current_mood->name is NULL, the dummy mood is currently open
+ * current_mood->name is NULL, the dummy mood is currently open.
  */
 static struct mood *current_mood;
 
-/**
- * Rough approximation to sqrt.
+/*
+ * Find the position of the most-significant set bit.
  *
- * \param x Integer of which to calculate the sqrt.
+ * Copied and slightly adapted from the linux source tree, version 4.9.39
+ * (2017-07).
+ */
+__a_const static uint32_t fls64(uint64_t v)
+{
+       int n = 63;
+       const uint64_t ones = ~(uint64_t)0U;
+
+       if ((v & (ones << 32)) == 0) {
+               n -= 32;
+               v <<= 32;
+       }
+       if ((v & (ones << (64 - 16))) == 0) {
+               n -= 16;
+               v <<= 16;
+       }
+       if ((v & (ones << (64 - 8))) == 0) {
+               n -= 8;
+               v <<= 8;
+       }
+       if ((v & (ones << (64 - 4))) == 0) {
+               n -= 4;
+               v <<= 4;
+       }
+       if ((v & (ones << (64 - 2))) == 0) {
+               n -= 2;
+               v <<= 2;
+       }
+       if ((v & (ones << (64 - 1))) == 0)
+               n -= 1;
+       return n;
+}
+
+/*
+ * Compute the integer square root floor(sqrt(x)).
  *
- * \return An integer res with res * res <= x.
+ * Taken 2007 from the linux source tree.
  */
 __a_const static uint64_t int_sqrt(uint64_t x)
 {
-       uint64_t op, res, one = 1;
-       op = x;
-       res = 0;
-
-       one = one << 62;
-       while (one > op)
-               one >>= 2;
+       uint64_t op = x, res = 0, one = 1;
 
+       one = one << (fls64(x) & ~one);
        while (one != 0) {
                if (op >= res + one) {
                        op = op - (res + one);
-                       res = res +  2 * one;
+                       res = res + 2 * one;
                }
                res /= 2;
                one /= 4;
        }
-//     PARA_NOTICE_LOG("sqrt(%llu) = %llu\n", x, res);
        return res;
 }
 
@@ -141,8 +173,8 @@ static bool get_item_score(struct mood_item *item, const struct afs_info *afsi,
 }
 
 /* returns 1 if row admissible, 0 if not, negative on errors */
-static int compute_mood_score(const struct osl_row *aft_row, struct mood *m,
-               long *result)
+static int row_is_admissible(const struct osl_row *aft_row, struct mood *m,
+               long *scorep)
 {
        struct mood_item *item;
        int ret;
@@ -154,14 +186,18 @@ static int compute_mood_score(const struct osl_row *aft_row, struct mood *m,
 
        if (!m)
                return -E_NO_MOOD;
+       if (m->parser_context) {
+               *scorep = 0;
+               return mp_eval_row(aft_row, m->parser_context);
+       }
        ret = get_afsi_of_row(aft_row, &afsi);
-       if (ret< 0)
+       if (ret < 0)
                return ret;
        ret = get_afhi_of_row(aft_row, &afhi);
-       if (ret< 0)
+       if (ret < 0)
                return ret;
        ret = get_audio_file_path_of_row(aft_row, &path);
-       if (ret< 0)
+       if (ret < 0)
                return ret;
        /* reject audio file if it matches any entry in the deny list */
        list_for_each_entry(item, &m->deny_list, mood_item_node) {
@@ -191,7 +227,7 @@ static int compute_mood_score(const struct osl_row *aft_row, struct mood *m,
        }
        if (score_arg_sum)
                score /= score_arg_sum;
-       *result = score;
+       *scorep = score;
        return 1;
 }
 
@@ -218,6 +254,7 @@ static void destroy_mood(struct mood *m)
        list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node)
                cleanup_list_entry(item);
        free(m->name);
+       mp_shutdown(m->parser_context);
        free(m);
 }
 
@@ -258,7 +295,6 @@ struct mood_line_parser_data {
  * <score> is either an integer or "random" which assigns a random score to
  * all matching files
  */
-
 static int parse_mood_line(char *mood_line, void *data)
 {
        struct mood_line_parser_data *mlpd = data;
@@ -372,7 +408,8 @@ out:
        return ret;
 }
 
-static int load_mood(const struct osl_row *mood_row, struct mood **m)
+static int load_mood(const struct osl_row *mood_row, struct mood **m,
+               char **errmsg)
 {
        char *mood_name;
        struct osl_object mood_def;
@@ -381,22 +418,31 @@ static int load_mood(const struct osl_row *mood_row, struct mood **m)
 
        *m = NULL;
        ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
-       if (ret < 0)
+       if (ret < 0) {
+               if (errmsg)
+                       *errmsg = make_message(
+                               "could not read mood definition");
                return ret;
-       if (!*mood_name)
-               return -E_DUMMY_ROW;
+       }
+       assert(*mood_name);
        mlpd.m = alloc_new_mood(mood_name);
        ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
                parse_mood_line, &mlpd);
-       osl_close_disk_object(&mood_def);
        if (ret < 0) {
-               PARA_ERROR_LOG("unable to load mood %s: %s\n", mlpd.m->name,
-                       para_strerror(-ret));
-               destroy_mood(mlpd.m);
-               return ret;
+               PARA_INFO_LOG("opening version 2 mood %s\n", mlpd.m->name);
+               ret = mp_init(mood_def.data, mood_def.size, &mlpd.m->parser_context,
+                       errmsg);
+               if (ret < 0)
+                       destroy_mood(mlpd.m);
+       } else {
+               PARA_WARNING_LOG("loaded version 1 mood %s\n", mlpd.m->name);
+               PARA_WARNING_LOG("please convert to version 2\n");
+               ret = 1;
        }
-       *m = mlpd.m;
-       return 1;
+       osl_close_disk_object(&mood_def);
+       if (ret >= 0)
+               *m = mlpd.m;
+       return ret;
 }
 
 static int check_mood(struct osl_row *mood_row, void *data)
@@ -414,12 +460,24 @@ static int check_mood(struct osl_row *mood_row, void *data)
        }
        if (!*mood_name) /* ignore dummy row */
                goto out;
-       para_printf(pb, "checking mood %s...\n", mood_name);
        ret = for_each_line(FELF_READ_ONLY, mood_def.data, mood_def.size,
                parse_mood_line, &mlpd);
-       if (ret < 0)
-               para_printf(pb, "mood %s: error in line %u: %s\n", mood_name,
-                       mlpd.line_num, para_strerror(-ret));
+       if (ret < 0) {
+               char *errmsg;
+               struct mood *m = alloc_new_mood("check");
+               ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
+                       &errmsg);
+               if (ret < 0) {
+                       para_printf(pb, "%s: %s\n", mood_name, errmsg);
+                       free(errmsg);
+                       para_printf(pb, "%s\n", para_strerror(-ret));
+               } else
+                       destroy_mood(m);
+       } else {
+               para_printf(pb, "%s: v1 mood, please convert to v2\n",
+                       mood_name);
+
+       }
        ret = 1; /* don't fail the loop on invalid mood definitions */
 out:
        osl_close_disk_object(&mood_def);
@@ -521,9 +579,7 @@ static int del_afs_statistics(const struct osl_row *row)
        return 1;
 }
 
-/**
- * Structure used during mood_open().
- *
+/*
  * At mood open time we determine the set of admissible files for the given
  * mood. The mood score of each admissible file is computed by adding up all
  * mood item scores. Next, we update the afs statistics and append a struct
@@ -534,8 +590,6 @@ static int del_afs_statistics(const struct osl_row *row)
  * the afs_statistics and the current time) to the mood score. Finally, all
  * audio files in the temporary array are added to the score table and the
  * array is freed.
- *
- * \sa mood_method, admissible_array.
  */
 struct admissible_file_info
 {
@@ -569,7 +623,7 @@ static int add_if_admissible(struct osl_row *aft_row, void *data)
        int ret;
        long score = 0;
 
-       ret = compute_mood_score(aft_row, aa->m, &score);
+       ret = row_is_admissible(aft_row, aa->m, &score);
        if (ret <= 0)
                return ret;
        if (statistics.num >= aa->size) {
@@ -628,7 +682,8 @@ _static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd,
        return old_qd + delta * (sigma - 2 * old_sum / n - delta / n);
 }
 
-static int update_afs_statistics(struct afs_info *old_afsi, struct afs_info *new_afsi)
+static int update_afs_statistics(struct afs_info *old_afsi,
+               struct afs_info *new_afsi)
 {
        unsigned n;
        int ret = get_num_admissible_files(&n);
@@ -676,7 +731,7 @@ static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
  *
  * \return Positive on success, negative on errors.
  *
- * \sa score_delete().
+ * \sa \ref score_delete().
  */
 static int mood_delete_audio_file(const struct osl_row *aft_row)
 {
@@ -715,7 +770,7 @@ static int mood_update_audio_file(const struct osl_row *aft_row,
        if (ret < 0)
                return ret;
        was_admissible = ret;
-       ret = compute_mood_score(aft_row, current_mood, &score);
+       ret = row_is_admissible(aft_row, current_mood, &score);
        if (ret < 0)
                return ret;
        is_admissible = (ret > 0);
@@ -779,8 +834,7 @@ static void log_statistics(void)
 /**
  * Close the current mood.
  *
- * Free all resources of the current mood which were allocated during
- * mood_open().
+ * Frees all resources of the current mood.
  */
 void close_current_mood(void)
 {
@@ -793,19 +847,22 @@ void close_current_mood(void)
  * Change the current mood.
  *
  * \param mood_name The name of the mood to open.
+ * \param errmsg Error description 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.
  *
+ * The errmsg pointer may be NULL, in which case no error message will be
+ * returned. If a non-NULL pointer is given, the caller must free *errmsg.
+ *
  * If there is already an open mood, it will be closed first.
  *
  * \return Positive on success, negative on errors. Loading the dummy mood
  * always succeeds.
  *
- * \sa struct admissible_file_info, struct admissible_array, struct
- * afs_info::last_played, mood_close().
+ * \sa struct \ref afs_info::last_played, \ref mp_eval_row().
  */
-int change_current_mood(const char *mood_name)
+int change_current_mood(const char *mood_name, char **errmsg)
 {
        int i, ret;
        struct admissible_array aa = {
@@ -822,10 +879,12 @@ int change_current_mood(const char *mood_name)
                };
                ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row));
                if (ret < 0) {
-                       PARA_NOTICE_LOG("no such mood: %s\n", mood_name);
+                       if (errmsg)
+                               *errmsg = make_message("no such mood: %s",
+                                       mood_name);
                        return ret;
                }
-               ret = load_mood(row, &m);
+               ret = load_mood(row, &m, errmsg);
                if (ret < 0)
                        return ret;
                close_current_mood();
@@ -837,13 +896,20 @@ int change_current_mood(const char *mood_name)
        aa.m = current_mood;
        PARA_NOTICE_LOG("computing statistics of admissible files\n");
        ret = audio_file_loop(&aa, add_if_admissible);
-       if (ret < 0)
+       if (ret < 0) {
+               if (errmsg)
+                       *errmsg = make_message("audio file loop failed");
                return ret;
+       }
        for (i = 0; i < statistics.num; i++) {
                struct admissible_file_info *a = aa.array + i;
                ret = add_to_score_table(a->aft_row, a->score);
-               if (ret < 0)
+               if (ret < 0) {
+                       if (errmsg)
+                               *errmsg = make_message(
+                                       "could not add row to score table");
                        goto out;
+               }
        }
        log_statistics();
        ret = statistics.num;
@@ -851,18 +917,15 @@ out:
        free(aa.array);
        return ret;
 }
-/**
+
+/*
  * Close and re-open the current mood.
  *
- * This function is used if changes to the audio file table or the
- * attribute table were made that render the current list of admissible
- * files useless. For example, if an attribute is removed from the
- * attribute table, this function is called.
- *
- * \return Positive on success, negative on errors. If no mood is currently
- * open, the function returns success.
+ * 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.
  *
- * \sa mood_open(), mood_close().
+ * If no mood is currently open, the function returns success.
  */
 static int reload_current_mood(void)
 {
@@ -879,7 +942,7 @@ static int reload_current_mood(void)
        if (current_mood->name)
                mood_name = para_strdup(current_mood->name);
        close_current_mood();
-       ret = change_current_mood(mood_name);
+       ret = change_current_mood(mood_name, NULL);
        free(mood_name);
        return ret;
 }
diff --git a/mood.h b/mood.h
index f7055753cf82fee8533f4f5fb0519ae4c86790d9..fcfe1efc8bdbd1175b32766b0d75f8395f84cbd0 100644 (file)
--- a/mood.h
+++ b/mood.h
@@ -1,11 +1,7 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file mood.h Public functions of mood.c. */
 
-int change_current_mood(const char *mood_name);
+int change_current_mood(const char *mood_name, char **errmsg);
 void close_current_mood(void);
 int mood_check_callback(struct afs_callback_arg *aca);
diff --git a/mp.c b/mp.c
new file mode 100644 (file)
index 0000000..416b4f9
--- /dev/null
+++ b/mp.c
@@ -0,0 +1,570 @@
+/* Copyright (C) 2017 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/**
+ * \file mp.c Mood parser helper functions.
+ *
+ * This file contains the public and the private API of the flex/bison based
+ * mood parser.
+ *
+ * The public API (at the bottom of the file) allows parsing the same mood
+ * definition many times in an efficient manner.
+ *
+ * The first function to call is \ref mp_init(), which analyzes the given mood
+ * definition syntactically. It returns the abstract syntax tree of the mood
+ * definition and pre-compiles all regular expression patterns to make later
+ * pattern matching efficient.
+ *
+ * Semantic analysis is performed in \ref mp_eval_row(). This function is
+ * called from \ref mood.c once for each file in the audio file table. It
+ * utilizes the abstract syntax tree and the pre-compiled regular expressions
+ * to determine the set of admissible audio files.
+ *
+ * If the mood is no longer needed, \ref mp_shutdown() should be called to free
+ * the resources.
+ *
+ * The internal API is described in \ref mp.h.
+ */
+
+#include "para.h"
+
+#include <regex.h>
+#include <fnmatch.h>
+#include <osl.h>
+#include <lopsub.h>
+
+#include "string.h"
+#include "error.h"
+#include "afh.h"
+#include "afs.h"
+#include "mp.h"
+#include "mp.bison.h"
+
+struct mp_context {
+       /* global context */
+       char *errmsg;
+       struct mp_ast_node *ast;
+       /* per audio file context */
+       const struct osl_row *aft_row;
+       char *path;
+       bool have_afsi;
+       struct afs_info afsi;
+       bool have_afhi;
+       struct afh_info afhi;
+};
+
+/**
+ * Parse a (generalized) string literal.
+ *
+ * \param src The string to parse.
+ * \param quote_chars Opening and closing quote characters.
+ * \param result The corresponding C string is returned here.
+ *
+ * This function turns a generalized C99 string literal like "xyz\n" into a C
+ * string (containing the three characters 'x', 'y' and 'z', followed by a
+ * newline character and the terminating zero byte). The function receives
+ * quote characters as an argument so that, for example, regular expression
+ * patterns enclosed in '/' can be parsed as well. To parse a proper string
+ * literal, one has to pass two double quotes as the second argument.
+ *
+ * The function strips off the opening and leading quote characters, replaces
+ * double backslashes by single backslashes and handles the usual escapes like
+ * \n and \".
+ *
+ * The caller must make sure that the input is well-formed. The function simply
+ * aborts if the input is not a valid C99 string literal (modulo the quote
+ * characters).
+ *
+ * \return Offset of the first character after the closing quote. For proper
+ * string literals this will be the terminating zero byte of the input string,
+ * for regular expression patterns it is the beginning of the flags which
+ * modify the matching behaviour.
+ *
+ * \sa \ref mp_parse_regex_pattern(), \ref mp_parse_wildcard_pattern().
+ */
+unsigned parse_quoted_string(const char *src, const char quote_chars[2],
+               char **result)
+{
+       size_t n, len = strlen(src);
+       char *dst, *p;
+       bool backslash;
+
+       assert(len >= 2);
+       assert(src[0] == quote_chars[0]);
+       p = dst = para_malloc(len - 1);
+       backslash = false;
+       for (n = 1;; n++) {
+               char c;
+               assert(n < len);
+               c = src[n];
+               if (!backslash) {
+                       if (c == '\\') {
+                               backslash = true;
+                               continue;
+                       }
+                       if (c == quote_chars[1])
+                               break;
+                       *p++ = c;
+                       continue;
+               }
+               if (c == quote_chars[1])
+                       *p++ = quote_chars[1];
+               else switch (c) {
+                       case '\\': *p++ = '\\'; break;
+                       case 'a': *p++ = '\a'; break;
+                       case 'b': *p++ = '\b'; break;
+                       case 'f': *p++ = '\f'; break;
+                       case 'n': *p++ = '\n'; break;
+                       case 'r': *p++ = '\r'; break;
+                       case 't': *p++ = '\t'; break;
+                       case 'v': *p++ = '\v'; break;
+                       default: assert(false);
+               }
+               backslash = false;
+       }
+       assert(src[n] == quote_chars[1]);
+       *p = '\0';
+       *result = dst;
+       return n + 1;
+}
+
+/**
+ * Parse and compile an extended regular expression pattern, including flags.
+ *
+ * \param src The pattern to parse.
+ * \param result C-string and flags are returned here.
+ *
+ * A regex pattern is identical to a C99 string literal except (a) it is
+ * enclosed in '/' characters rather than double quotes, (b) double quote
+ * characters which are part of the pattern do not need to be quoted with
+ * backslashes, but slashes must be quoted in this way, and (c) the closing
+ * slash may be followed by one or more flag characters which modify the
+ * matching behaviour.
+ *
+ * The only flags which are currently supported are 'i' to ignore case in match
+ * (REG_ICASE) and 'n' to change the handling of newline characters
+ * (REG_NEWLINE).
+ *
+ * \return Standard. This function calls \ref parse_quoted_string(), hence it
+ * aborts if the input string is malformed. However, errors from \ref
+ * para_regcomp are returned without aborting the process. The rationale behind
+ * this difference is that passing a malformed string must be considered an
+ * implementation bug because malformed strings should be rejected earlier by
+ * the lexer.
+ *
+ * \sa \ref mp_parse_wildcard_pattern(), \ref parse_quoted_string(),
+ * \ref para_regcomp(), regex(3).
+ */
+int mp_parse_regex_pattern(const char *src, struct mp_re_pattern *result)
+{
+       int ret;
+       char *pat;
+       unsigned n = parse_quoted_string(src, "//", &pat);
+
+       result->flags = 0;
+       for (; src[n]; n++) {
+               switch (src[n]) {
+               case 'i': result->flags |= REG_ICASE; break;
+               case 'n': result->flags |= REG_NEWLINE; break;
+               default: assert(false);
+               }
+       }
+       ret = para_regcomp(&result->preg, pat, result->flags);
+       free(pat);
+       return ret;
+}
+
+/**
+ * Parse a wildcard pattern, including flags.
+ *
+ * \param src The pattern to parse.
+ * \param result C-string and flags are returned here.
+ *
+ * This function parses a shell wildcard pattern. It is similar to \ref
+ * mp_parse_regex_pattern(), so the remarks mentioned there apply to this
+ * function as well.
+ *
+ * Wildcard patterns differ from regular expression patterns in that (a) they
+ * must be enclosed in '|' characters, (b) they support different flags for
+ * modifying matching behaviour, and (c) there is no cache for them.
+ *
+ * The following flags, whose meaning is explained in fnmatch(3), are currently
+ * supported: 'n' (FNM_NOESCAPE), 'p' (FNM_PATHNAME), 'P' (FNM_PERIOD), 'l'
+ * (FNM_LEADING_DIR), 'i' (FNM_CASEFOLD), 'e' (FNM_EXTMATCH). The last flag is
+ * a GNU extension. It is silently ignored on non GNU systems.
+ *
+ * \sa \ref parse_quoted_string(), \ref mp_parse_regex_pattern(), fnmatch(3).
+ */
+void mp_parse_wildcard_pattern(const char *src, struct mp_wc_pattern *result)
+{
+       unsigned n = parse_quoted_string(src, "||", &result->pat);
+
+       result->flags = 0;
+       for (; src[n]; n++) {
+               switch (src[n]) {
+               case 'n': result->flags |= FNM_NOESCAPE; break;
+               case 'p': result->flags |= FNM_PATHNAME; break;
+               case 'P': result->flags |= FNM_PERIOD; break;
+               /* not POSIX, but both FreeBSD and NetBSD have it */
+               case 'l': result->flags |= FNM_LEADING_DIR; break;
+               case 'i': result->flags |= FNM_CASEFOLD; break;
+               /* GNU only */
+#ifdef HAVE_FNM_EXTMATCH
+               case 'e': result->flags |= FNM_EXTMATCH; break;
+#else /* silently ignore extglob flag */
+               case 'e': break;
+#endif
+               default: assert(false);
+               }
+       }
+}
+
+/**
+ * Set the error bit in the parser context and log a message.
+ *
+ * \param line The number of the input line which caused the error.
+ * \param ctx Contains the error bit.
+ * \param fmt Usual format string.
+ *
+ * This is called if the lexer or the parser detect an error in the mood
+ * definition. Only the first error is logged (with a severity of "warn").
+ */
+__printf_3_4 void mp_parse_error(int line, struct mp_context *ctx,
+               const char *fmt, ...)
+{
+       va_list ap;
+       char *tmp;
+
+       if (ctx->errmsg) /* we already printed an error message */
+               return;
+       va_start(ap, fmt);
+       xvasprintf(&tmp, fmt, ap);
+       va_end(ap);
+       xasprintf(&ctx->errmsg, "line %d: %s", line, tmp);
+       free(tmp);
+       PARA_WARNING_LOG("%s\n", ctx->errmsg);
+}
+
+static int get_afsi(struct mp_context *ctx)
+{
+       int ret;
+
+       if (ctx->have_afsi)
+               return 0;
+       ret = get_afsi_of_row(ctx->aft_row, &ctx->afsi);
+       if (ret < 0)
+               return ret;
+       ctx->have_afsi = true;
+       return 1;
+}
+
+static int get_afhi(struct mp_context *ctx)
+{
+       int ret;
+
+       if (ctx->have_afhi)
+               return 0;
+       ret = get_afhi_of_row(ctx->aft_row, &ctx->afhi);
+       if (ret < 0)
+               return ret;
+       ctx->have_afhi = true;
+       return 1;
+}
+
+/**
+ * Return the full path to the audio file.
+ *
+ * \param ctx Contains a reference to the row of the audio file table which
+ * corresponds to the current audio file. The path of the audio file, the
+ * afs_info and the afh_info structures (which contain the tag information) can
+ * be retrieved through this reference.
+ *
+ * \return A reference to the path. Must not be freed by the caller.
+ *
+ * \sa \ref get_audio_file_path_of_row().
+ */
+char *mp_path(struct mp_context *ctx)
+{
+       if (!ctx->path)
+               get_audio_file_path_of_row(ctx->aft_row, &ctx->path);
+       return ctx->path;
+}
+
+/**
+ * Check whether the given attribute is set for the current audio file.
+ *
+ * \param attr The string to look up in the attribute table.
+ * \param ctx See \ref mp_path().
+ *
+ * First, determine the bit number which corresponds to the attribute, then
+ * check if this bit is set in the ->attributes field of the afs_info structure
+ * of the audio file.
+ *
+ * \return True if the attribute is set, false if it is not. On errors, for
+ * example if the given string is no attribute, the function returns false.
+ *
+ * \sa \ref get_attribute_bitnum_by_name().
+ */
+bool mp_is_set(const char *attr, struct mp_context *ctx)
+{
+       int ret;
+       unsigned char bitnum;
+       const uint64_t one = 1;
+
+       ret = get_attribute_bitnum_by_name(attr, &bitnum);
+       if (ret < 0) /* treat invalid attributes as not set */
+               return false;
+       ret = get_afsi(ctx);
+       if (ret < 0)
+               return false;
+       return (one << bitnum) & ctx->afsi.attributes;
+}
+
+/**
+ * Count the number of attributes set.
+ *
+ * \param ctx See \ref mp_path().
+ *
+ * \return The number of bits which are set in the ->attributes field of the
+ * afs_info structure of the current audio file.
+ */
+int64_t mp_num_attributes_set(struct mp_context *ctx)
+{
+       const uint64_t m = ~(uint64_t)0;
+       int ret;
+       uint64_t v;
+
+       ret = get_afsi(ctx);
+       if (ret < 0)
+               return 0;
+
+       v = ctx->afsi.attributes;
+       /* taken from https://graphics.stanford.edu/~seander/bithacks.html */
+       v = v - ((v >> 1) & m / 3);
+       v = (v & m / 15 * 3) + ((v >> 2) & m / 15 * 3);
+       v = (v + (v >> 4)) & m / 255 * 15;
+       v = (v * (m / 255)) >> 56;
+       assert(v <= 64);
+       return v;
+}
+
+/**
+ * Define a function which returns a field of the afs_info structure.
+ *
+ * \param _name The name of the field.
+ *
+ * The defined function casts the value to int64_t. On errors, zero is returned.
+ */
+#define MP_AFSI(_name) \
+       int64_t mp_ ## _name(struct mp_context *ctx) \
+       { \
+               int ret = get_afsi(ctx); \
+               if (ret < 0) \
+                       return 0; \
+               return ctx->afsi._name; \
+       }
+/** \cond MP_AFSI */
+MP_AFSI(num_played)
+MP_AFSI(image_id)
+MP_AFSI(lyrics_id)
+/** \endcond */
+
+/**
+ * Define a function which returns a field of the afh_info structure.
+ *
+ * \param _name The name of the field.
+ *
+ * The defined function casts the value to int64_t. On errors, zero is returned.
+ */
+#define MP_AFHI(_name) \
+       int64_t mp_ ## _name(struct mp_context *ctx) \
+       { \
+               int ret = get_afhi(ctx); \
+               if (ret < 0) \
+                       return 0; \
+               return ctx->afhi._name; \
+       }
+/** \cond MP_AFHI */
+MP_AFHI(bitrate)
+MP_AFHI(frequency)
+MP_AFHI(channels)
+/** \endcond */
+
+/**
+ * Define a function which extracts and returns the value of a meta tag.
+ *
+ * \param _name The name of the tag (artist, title, ...).
+ *
+ * The function will return a pointer to memory owned by the audio file
+ * selector. On errors, or if the current audio file has no tag of the given
+ * name, the function returns the empty string. The caller must not attempt to
+ * free the returned string.
+ */
+#define MP_TAG(_name) \
+       char *mp_ ## _name (struct mp_context *ctx) \
+       { \
+               int ret = get_afhi(ctx); \
+               if (ret < 0) \
+                       return ""; \
+               return ctx->afhi.tags._name; \
+       }
+/** \cond MP_TAG */
+MP_TAG(artist)
+MP_TAG(title)
+MP_TAG(album)
+MP_TAG(comment)
+/** \endcond */
+
+/**
+ * Parse and return the value of the year tag.
+ *
+ * \param ctx See \ref mp_path().
+ *
+ * \return If the year tag is not present, can not be parsed, or its value is
+ * less than zero, the function returns 0. If the value is less than 100, we
+ * add 1900.
+ */
+int64_t mp_year(struct mp_context *ctx)
+{
+       int64_t year;
+       int ret = get_afhi(ctx);
+
+       if (ret < 0)
+               return 0;
+       assert(ctx->afhi.tags.year);
+       ret = para_atoi64(ctx->afhi.tags.year, &year);
+       if (ret < 0)
+               return 0;
+       if (year < 0)
+               return 0;
+       if (year < 100)
+               year += 1900;
+       return year;
+}
+
+/*
+ * Ideally, these functions should be declared in a header file which is
+ * created by flex with the --header-file option. However, for flex-2.6.x
+ * (2017) this option is borken: if --reentrant is also given, the generated
+ * header file contains syntax errors. As a workaround we declare the functions
+ * here.
+ */
+/** \cond flex_workaround */
+int mp_yylex_init(mp_yyscan_t *yyscanner);
+struct yy_buffer_state *mp_yy_scan_bytes(const char *buf, int len,
+       mp_yyscan_t yyscanner);
+void mp_yy_delete_buffer(struct yy_buffer_state *bs, mp_yyscan_t yyscanner);
+int mp_yylex_destroy(mp_yyscan_t yyscanner);
+void mp_yyset_lineno(int lineno, mp_yyscan_t scanner);
+/** \endcond */
+
+/* Public API */
+
+/**
+ * Initialize the mood parser.
+ *
+ * This allocates and sets up the internal structures of the mood parser
+ * and creates an abstract syntax tree from the given mood definition.
+ * It must be called before \ref mp_eval_row() can be called.
+ *
+ * The context pointer returned by this function may be passed to \ref
+ * mp_eval_row() to determine whether an audio file is admissible.
+ *
+ * \param definition A reference to the mood definition.
+ * \param nbytes The size of the mood definition.
+ * \param result Opaque context pointer is returned here.
+ * \param errmsg Optional error message is returned here.
+ *
+ * It's OK to pass a NULL pointer or a zero sized buffer as the mood
+ * definition. This corresponds to the "dummy" mood for which all audio files
+ * are admissible.
+ *
+ * The error message pointer may also be NULL in which case no error message
+ * is returned. Otherwise, the caller must free the returned string.
+ *
+ * \return Standard. On success *errmsg is set to NULL.
+ */
+int mp_init(const char *definition, int nbytes, struct mp_context **result,
+                char **errmsg)
+{
+       int ret;
+       mp_yyscan_t scanner;
+       struct mp_context *ctx;
+       struct yy_buffer_state *buffer_state;
+
+       *result = NULL;
+       if (!definition || nbytes == 0) { /* dummy mood */
+               if (errmsg)
+                       *errmsg = NULL;
+               return 0;
+       }
+       ctx = para_calloc(sizeof(*ctx));
+       ctx->errmsg = NULL;
+       ctx->ast = NULL;
+
+       ret = mp_yylex_init(&scanner);
+       assert(ret == 0);
+       buffer_state = mp_yy_scan_bytes(definition, nbytes, scanner);
+       mp_yyset_lineno(1, scanner);
+       PARA_NOTICE_LOG("creating abstract syntax tree\n");
+       ret = mp_yyparse(ctx, &ctx->ast, scanner);
+       mp_yy_delete_buffer(buffer_state, scanner);
+       mp_yylex_destroy(scanner);
+       if (ctx->errmsg) { /* parse error */
+               if (errmsg)
+                       *errmsg = ctx->errmsg;
+               else
+                       free(ctx->errmsg);
+               free(ctx);
+               return -E_MOOD_PARSE;
+       }
+       if (errmsg)
+               *errmsg = NULL;
+       *result = ctx;
+       return 1;
+}
+
+/**
+ * Determine whether the given audio file is admissible.
+ *
+ * \param aft_row The audio file to check for admissibility.
+ * \param ctx As returned from \ref mp_init().
+ *
+ * \return Whether the audio file is admissible.
+ *
+ * If the mood parser was set up without an input buffer (dummy mood), this
+ * function returns true (without looking at the audio file metadata) to
+ * indicate that the given audio file should be considered admissible.
+ *
+ * \sa \ref change_current_mood(), \ref mp_eval_ast().
+ */
+bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx)
+{
+       if (!ctx) /* dummy mood */
+               return true;
+       if (!ctx->ast) /* empty mood */
+               return true;
+       assert(aft_row);
+       ctx->aft_row = aft_row;
+       ctx->have_afsi = false;
+       ctx->have_afhi = false;
+       ctx->path = NULL;
+       return mp_eval_ast(ctx->ast, ctx);
+}
+
+/**
+ * Deallocate the resources of a mood parser.
+ *
+ * This function frees the abstract syntax tree which was created by \ref
+ * mp_init().
+ *
+ * \param ctx As returned from \ref mp_init().
+ *
+ * It's OK to pass a NULL pointer, in which case the function does nothing.
+ */
+void mp_shutdown(struct mp_context *ctx)
+{
+       if (!ctx)
+               return;
+       mp_free_ast(ctx->ast);
+       free(ctx);
+}
diff --git a/mp.h b/mp.h
new file mode 100644 (file)
index 0000000..febbe32
--- /dev/null
+++ b/mp.h
@@ -0,0 +1,163 @@
+/* Copyright (C) 2017 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/**
+ * \file mp.h Internal mood parser API (backend).
+ *
+ * This header is included from the lexer, the parser, and from \ref mp.c, but
+ * not from \ref mood.c, the only user of the mood parser front end. It
+ * contains structures and function prototypes which are considered
+ * implementation details.
+ *
+ * There is one function for each keyword in the context-free grammar of the
+ * parser. These functions return the semantic value of the keyword.
+ *
+ * The functions declared here are defined either in mp.c or in mp.y.
+ */
+
+/** Opaque, only known to mp.c. Passed to the generated mp_yyparse(). */
+struct mp_context;
+
+/**
+ * Since we use a reentrant lexer, all functions generated by flex(1)
+ * receive an additional argument of this type.
+ */
+typedef void *mp_yyscan_t;
+
+/** Parsed regex pattern. */
+struct mp_re_pattern {
+       regex_t preg; /**< Pre-compiled regex. **/
+       unsigned flags; /**< Subset of the cflags described in regex(3). */
+};
+
+/** Parsed wildcard pattern. */
+struct mp_wc_pattern {
+       char *pat; /**< Unescaped C string (without quotes and flags). */
+       unsigned flags; /**< For modifying matching behaviour. */
+};
+
+/**
+ * The possible values of a node in the abstract syntax tree (AST).
+ *
+ * Constant semantic values (string literals, numeric constants, wildcard and
+ * regex patterns which are part of the mood definition) are determined during
+ * \ref mp_init() while values which depend on the audio file (path, bitrate,
+ * etc.) are determined during mp_eval_row().
+ *
+ * This union, and the \ref mp_ast_node structure below are used extensively in
+ * mp.y. However, both need to be public because the lexer must be able to
+ * create AST nodes for the constant semantic values.
+ */
+union mp_semantic_value {
+       bool boolval; /**< Comparators, =~ and =|. */
+       char *strval; /**< String literals, tags, path. */
+       int64_t intval; /**< Constants, bitrate, frequency, etc. */
+       struct mp_wc_pattern wc_pattern; /**< Right-hand side operand of =|. */
+       struct mp_re_pattern re_pattern; /**< Right-hand side operand of =~. */
+};
+
+/**
+ * Describes one node of the abstract syntax tree.
+ *
+ * A node is either interior or a leaf node. Interior nodes have at least one
+ * child while leaf nodes have a semantic value and no children.
+ *
+ * Examples: (a) STRING_LITERAL has a semantic value (the unescaped string
+ * literal) and no children, (b) NEG (unary minus) has no semantic value but
+ * one child (the numeric expression that is to be negated), (c) LESS_OR_EQUAL
+ * has no semantic value and two children (the two numeric expressions being
+ * compared).
+ */
+struct mp_ast_node {
+       /** Corresponds to a token type, for example LESS_OR_EQUAL. */
+       int id;
+       union {
+               /** Pointers to the child nodes (interior nodes only). */
+               struct mp_ast_node **children;
+               /** Leaf nodes only. */
+               union mp_semantic_value sv;
+       };
+       /**
+        * The number of children is implicitly given by the id, but we include
+        * it here to avoid having to maintain a lookup table. The AST is
+        * usually small, so we can afford to waste a byte per node.
+        */
+       uint8_t num_children;
+};
+
+/* Called from both the lexer and the parser. */
+__printf_3_4 void mp_parse_error(int line, struct mp_context *ctx,
+               const char *fmt, ...);
+
+/* Helper functions for the lexer. */
+unsigned parse_quoted_string(const char *src, const char quote_chars[2],
+               char **result);
+int mp_parse_regex_pattern(const char *src, struct mp_re_pattern *result);
+void mp_parse_wildcard_pattern(const char *src, struct mp_wc_pattern *result);
+
+/*
+ * The functions below are implemented in mp.y. They are documented here
+ * because mp.y is not doxyfied.
+ */
+
+/**
+ * Allocate a new leaf node for the abstract syntax tree.
+ *
+ * \param id Initial value for the ->id field of the new node
+ *
+ * \return Pointer to a node whose ->num_children field is initialized to zero.
+ * The caller is expected to initialize the ->sv field.
+ */
+struct mp_ast_node *mp_new_ast_leaf_node(int id);
+
+/**
+ * Evaluate an abstract syntax tree, starting at the root node.
+ *
+ * \param root As returned from \ref mp_init() via the context pointer.
+ * \param ctx Contains the aft row to evaluate.
+ *
+ * \return True if the AST evaluates to true, a non-empty string, or a
+ * non-zero number. False otherwise.
+ *
+ * \sa mp_eval_row().
+ */
+bool mp_eval_ast(struct mp_ast_node *root, struct mp_context *ctx);
+
+/**
+ * Deallocate an abstract syntax tree.
+ *
+ * This frees the memory occupied by the nodes of the AST, the child pointers
+ * of the internal nodes and the (constant) semantic values of the leaf nodes
+ * (string literals, unescaped wildcard patterns and pre-compiled regular
+ * expressions).
+ *
+ * \param root It's OK to pass NULL here.
+ */
+void mp_free_ast(struct mp_ast_node *root);
+
+/* Helper functions for the parser. */
+bool mp_is_set(const char *attr, struct mp_context *ctx);
+char *mp_path(struct mp_context *ctx);
+int64_t mp_year(struct mp_context *ctx);
+int64_t mp_num_attributes_set(struct mp_context *ctx);
+
+/* Generated with MP_AFSI() */
+/** \cond MP_AFSI */
+int64_t mp_num_played(struct mp_context *ctx);
+int64_t mp_image_id(struct mp_context *ctx);
+int64_t mp_lyrics_id(struct mp_context *ctx);
+/** \endcond */
+
+/* Generated with MP_AFHI() */
+/** \cond MP_AFHI */
+int64_t mp_bitrate(struct mp_context *ctx);
+int64_t mp_frequency(struct mp_context *ctx);
+int64_t mp_channels(struct mp_context *ctx);
+/** \endcond */
+
+/* Generated with MP_TAG() */
+/** \cond MP_TAG */
+char *mp_artist(struct mp_context *ctx);
+char *mp_title(struct mp_context *ctx);
+char *mp_album(struct mp_context *ctx);
+char *mp_comment(struct mp_context *ctx);
+/** \endcond */
index 2115f71c77ce745b356b917058792d0c64db42ef..728b25b81f94aa177a1e74ae01fca07419f7fe90 100644 (file)
--- a/mp3_afh.c
+++ b/mp3_afh.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2003 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2003 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file mp3_afh.c para_server's mp3 audio format handler */
 
@@ -67,8 +63,6 @@ static const int mp3info_bitrate[2][3][14] = {
 };
 
 static const int frame_size_index[] = {24000, 72000, 72000};
-static const char *mode_text[] = {"stereo", "joint stereo", "dual channel", "mono", "invalid"};
-
 #ifdef HAVE_ID3TAG
 
 #include <id3tag.h>
@@ -437,10 +431,13 @@ static int header_frequency(struct mp3header *h)
        return frequencies[h->version][h->freq];
 }
 
-static const char *header_mode(struct mp3header *h)
+static const char *header_mode(const struct mp3header *h)
 {
-       if (h->mode > 4)
-               h->mode = 4; /* invalid */
+       const char * const mode_text[] = {"stereo", "joint stereo",
+               "dual channel", "mono"};
+
+       if (h->mode >= ARRAY_SIZE(mode_text))
+               return "invalid";
        return mode_text[h->mode];
 }
 
@@ -655,8 +652,9 @@ static int mp3_read_info(unsigned char *map, size_t numbytes, int fd,
        afhi->channels = header_channels(&header);
        afhi->seconds_total = (tv2ms(&total_time) + 500) / 1000;
        tv_divide(afhi->chunks_total, &total_time, &afhi->chunk_tv);
-       PARA_DEBUG_LOG("%" PRIu32 "chunks, each %lums\n", afhi->chunks_total,
+       PARA_DEBUG_LOG("%" PRIu32 " chunks, each %lums\n", afhi->chunks_total,
                tv2ms(&afhi->chunk_tv));
+       set_max_chunk_size(afhi);
        ret = mp3_get_id3(map, numbytes, fd, &afhi->tags);
        afhi->techinfo = make_message("%cbr, %s, %s tags", vbr? 'v' : 'c',
                header_mode(&header), tag_versions[ret]);
@@ -677,7 +675,7 @@ static int mp3_get_file_info(char *map, size_t numbytes, int fd,
        ret = mp3_read_info((unsigned char *)map, numbytes, fd, afhi);
        if (ret < 0)
                return ret;
-       if (afhi->seconds_total < 2 || !afhi->chunks_total)
+       if (afhi->chunks_total == 0)
                return -E_MP3_INFO;
        return 1;
 }
@@ -685,15 +683,14 @@ static int mp3_get_file_info(char *map, size_t numbytes, int fd,
 static const char * const mp3_suffixes[] = {"mp3", NULL};
 
 /**
- * the init function of the mp3 audio format handler
+ * The mp3 audio format handler.
  *
- * \param afh pointer to the struct to initialize
+ * It does not depend on any libraries and is hence always compiled in.
  */
-void mp3_init(struct audio_format_handler *afh)
-{
-       afh->get_file_info = mp3_get_file_info;
-       afh->suffixes = mp3_suffixes;
+const struct audio_format_handler mp3_afh = {
+       .get_file_info = mp3_get_file_info,
+       .suffixes = mp3_suffixes,
 #ifdef HAVE_LIBID3TAG
-       afh->rewrite_tags = mp3_rewrite_tags;
+       .rewrite_tags = mp3_rewrite_tags,
 #endif /* HAVE_LIBID3TAG */
-}
+};
index 5f6935d1a5be70c7fa89dd0368bca48e34664422..ccb1553b820357e13f2cafe2c0e8a7e0502ac8fd 100644 (file)
@@ -1,19 +1,15 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file mp3dec_filter.c Paraslash's mp3 decoder. */
 
 #include <mad.h>
 #include <regex.h>
+#include <lopsub.h>
 
+#include "filter_cmd.lsg.h"
 #include "para.h"
-#include "mp3dec_filter.cmdline.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "error.h"
@@ -171,25 +167,15 @@ err:
 static void mp3dec_open(struct filter_node *fn)
 {
        struct private_mp3dec_data *pmd = para_calloc(sizeof(*pmd));
-       struct mp3dec_filter_args_info *mp3_conf = fn->conf;
 
        fn->private_data = pmd;
        mad_stream_init(&pmd->stream);
        mad_frame_init(&pmd->frame);
        mad_synth_init(&pmd->synth);
-       if (mp3_conf->ignore_crc_given)
+       if (FILTER_CMD_OPT_GIVEN(MP3DEC, IGNORE_CRC, fn->lpr))
                mad_stream_options(&pmd->stream, MAD_OPTION_IGNORECRC);
 }
 
-static int mp3dec_parse_config(int argc, char **argv, void **config)
-{
-       struct mp3dec_filter_args_info *conf = para_calloc(sizeof(*conf));
-
-       mp3dec_filter_cmdline_parser(argc, argv, conf);
-       *config = conf;
-       return 1;
-}
-
 static int mp3dec_execute(struct btr_node *btrn, const char *cmd, char **result)
 {
        struct filter_node *fn = btr_context(btrn);
@@ -198,28 +184,10 @@ static int mp3dec_execute(struct btr_node *btrn, const char *cmd, char **result)
        return decoder_execute(cmd, pmd->sample_rate, pmd->channels, result);
 }
 
-static void mp3dec_free_config(void *conf)
-{
-       mp3dec_filter_cmdline_parser_free(conf);
-}
-/**
- * The init function of the mp3dec filter.
- *
- * \param f Pointer to the filter struct to initialize.
- *
- * \sa filter::init.
- */
-void mp3dec_filter_init(struct filter *f)
-{
-       struct mp3dec_filter_args_info dummy;
-
-       mp3dec_filter_cmdline_parser_init(&dummy);
-       f->open = mp3dec_open;
-       f->close = mp3dec_close;
-       f->parse_config = mp3dec_parse_config;
-       f->free_config = mp3dec_free_config;
-       f->pre_select = generic_filter_pre_select;
-       f->post_select = mp3dec_post_select;
-       f->execute = mp3dec_execute;
-       f->help = (struct ggo_help)DEFINE_GGO_HELP(mp3dec_filter);
-}
+const struct filter lsg_filter_cmd_com_mp3dec_user_data = {
+       .open = mp3dec_open,
+       .close = mp3dec_close,
+       .pre_select = generic_filter_pre_select,
+       .post_select = mp3dec_post_select,
+       .execute = mp3dec_execute,
+};
diff --git a/net.c b/net.c
index eaa1cfb81146b3dc4fd7bb11cd41df447dc0e825..91200fc040bcfebafccf9e737fb65514af9ff8f2 100644 (file)
--- a/net.c
+++ b/net.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file net.c Networking-related helper functions. */
 
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netdb.h>
-
-/* At least NetBSD needs these. */
-#ifndef AI_V4MAPPED
-#define AI_V4MAPPED 0
-#endif
-#ifndef AI_ALL
-#define AI_ALL 0
-#endif
-#ifndef AI_ADDRCONFIG
-#define AI_ADDRCONFIG 0
-#endif
-
 #include <regex.h>
 
 #include "error.h"
@@ -44,7 +28,7 @@
  *               default of 32 if not specified.
  *
  * \return Pointer to \a addr if successful, NULL on error.
- * \sa RFC 4632
+ * \sa RFC 4632.
  */
 char *parse_cidr(const char *cidr,
                 char    *addr, ssize_t addrlen,
@@ -104,7 +88,7 @@ static bool is_v4_dot_quad(const char *address)
  * \param host The host string to check.
  * \return True if \a host passes the syntax checks.
  *
- * \sa RFC 3986, 3.2.2; RFC 1123, 2.1; RFC 1034, 3.5
+ * \sa RFC 3986, 3.2.2; RFC 1123, 2.1; RFC 1034, 3.5.
  */
 static bool host_string_ok(const char *host)
 {
@@ -146,7 +130,7 @@ static bool host_string_ok(const char *host)
  * \a host and \a port are undefined. If no port number was present in \a url,
  * \a port is set to -1.
  *
- * \sa RFC 3986, 3.2.2/3.2.3
+ * \sa RFC 3986, 3.2.2/3.2.3.
  */
 char *parse_url(const char *url,
                char    *host, ssize_t hostlen,
@@ -184,6 +168,36 @@ failed:
        return NULL;
 }
 
+/**
+ * Pretty-print a host/port pair.
+ *
+ * \param url NULL, or any string accepted by \ref parse_url().
+ * \param default_port Applies if url has no port.
+ *
+ * If the url argument is NULL, the function returns the string
+ * 0.0.0.0:default_port. Otherwise it calls \ref parse_url() to check the
+ * syntax of the input string given by url. On errors the string "?" is
+ * returned. Otherwise, if url contains a port, a copy of url is returned. If
+ * no port was supplied, a colon and the default port are appended to url.
+ *
+ * \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)
+{
+       char host[MAX_HOSTLEN];
+       int url_port;
+
+       if (!url)
+               return make_message("0.0.0.0:%d", default_port);
+       if (!parse_url(url, host, sizeof(host), &url_port))
+               return make_message("?");
+       if (url_port < 0)
+               return make_message("%s:%d", url, default_port);
+       else
+               return para_strdup(url);
+}
+
 /**
  * Stringify port number, resolve into service name where defined.
  *
@@ -191,7 +205,7 @@ failed:
  * \param transport Transport protocol name (e.g. "udp", "tcp"), or NULL.
  * \return Pointer to static result buffer.
  *
- * \sa getservent(3), services(5), nsswitch.conf(5)
+ * \sa getservent(3), services(5), nsswitch.conf(5).
  */
 const char *stringify_port(int port, const char *transport)
 {
@@ -215,7 +229,7 @@ const char *stringify_port(int port, const char *transport)
  *
  * \param l4type The symbolic name of the transport-layer protocol.
  *
- * \sa ip(7), socket(2)
+ * \sa ip(7), socket(2).
  */
 static inline int sock_type(const unsigned l4type)
 {
@@ -247,7 +261,7 @@ static const char *layer4_name(const unsigned l4type)
  * directly after makesock(). The 'pre_conn_opt' structure is for internal use
  * only and should not be visible elsewhere.
  *
- * \sa setsockopt(2), makesock()
+ * \sa setsockopt(2), \ref makesock().
  */
 struct pre_conn_opt {
        int             sock_level;     /**< Second argument to setsockopt() */
@@ -288,7 +302,7 @@ struct flowopts *flowopt_new(void)
  * \param val  The value to set \a opt to.
  * \param len  Length of \a val.
  *
- *  \sa setsockopt(2)
+ *  \sa setsockopt(2).
  */
 void flowopt_add(struct flowopts *fo, int lev, int opt,
                const char *name, const void *val, int len)
@@ -429,15 +443,20 @@ int makesock_addrinfo(unsigned l4type, bool passive, struct addrinfo *ai,
        for (; ai; ai = ai->ai_next) {
                int fd;
                ret = socket(ai->ai_family, sock_type(l4type), l4type);
-               if (ret < 0)
+               if (ret < 0) {
+                       PARA_NOTICE_LOG("socket(): %s\n", strerror(errno));
                        continue;
+               }
                fd = ret;
                flowopt_setopts(fd, fo);
                if (!passive) {
-                       if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0)
-                               return fd;
-                       close(fd);
-                       continue;
+                       if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                               PARA_NOTICE_LOG("connect(): %s\n",
+                                       strerror(errno));
+                               close(fd);
+                               continue;
+                       }
+                       return fd;
                }
                /*
                 * Reuse the address on passive sockets to avoid failure on
@@ -446,10 +465,12 @@ int makesock_addrinfo(unsigned l4type, bool passive, struct addrinfo *ai,
                 */
                if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on,
                                sizeof(on)) == -1) {
+                       PARA_NOTICE_LOG("setsockopt(): %s\n", strerror(errno));
                        close(fd);
                        continue;
                }
                if (bind(fd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                       PARA_NOTICE_LOG("bind(): %s\n", strerror(errno));
                        close(fd);
                        continue;
                }
@@ -499,18 +520,28 @@ int makesock(unsigned l4type, bool passive, const char *host, uint16_t port_numb
  * Create a passive / listening socket.
  *
  * \param l4type The transport-layer type (\p IPPROTO_xxx).
- * \param port The decimal port number to listen on.
- * \param fo Flowopts (if any) to set before starting to listen.
+ * \param addr Passed to \ref parse_url() if not NULL.
+ * \param port Ignored if addr contains a port number.
  *
  * \return Positive integer (socket descriptor) on success, negative value
  * otherwise.
  *
- * \sa makesock(), ip(7), ipv6(7), bind(2), listen(2).
+ * \sa \ref makesock(), ip(7), ipv6(7), bind(2), listen(2).
  */
-int para_listen(unsigned l4type, uint16_t port, struct flowopts *fo)
+int para_listen(unsigned l4type, const char *addr, uint16_t port)
 {
-       int ret, fd = makesock(l4type, 1, NULL, port, fo);
-
+       char host[MAX_HOSTLEN];
+       int ret, fd, addr_port;
+
+       if (addr) {
+               if (!parse_url(addr, host, sizeof(host), &addr_port))
+                       return -ERRNO_TO_PARA_ERROR(EINVAL);
+               if (addr_port > 0)
+                       port = addr_port;
+               addr = host;
+       }
+       fd = makesock(l4type, true /* passive */, addr, port,
+               NULL /* no flowopts */);
        if (fd > 0) {
                ret = listen(fd, BACKLOG);
                if (ret < 0) {
@@ -524,6 +555,22 @@ int para_listen(unsigned l4type, uint16_t port, struct flowopts *fo)
        return fd;
 }
 
+/**
+ * Create a socket which listens on all network addresses.
+ *
+ * \param l4type See \ref para_listen().
+ * \param port See \ref para_listen().
+ *
+ * This is a simple wrapper for \ref para_listen() which passes a NULL pointer
+ * as the address information.
+ *
+ * \return See \ref para_listen().
+ */
+int para_listen_simple(unsigned l4type, uint16_t port)
+{
+       return para_listen(l4type, NULL, port);
+}
+
 /**
  * Determine IPv4/v6 socket address length.
  * \param sa Container of IPv4 or IPv6 address.
@@ -551,7 +598,7 @@ static bool SS_IS_ADDR_V4MAPPED(const struct sockaddr_storage *ss)
  * \param ss Container of IPv4/6 address.
  * \return Pointer to normalized address (may be static storage).
  *
- * \sa RFC 3493
+ * \sa RFC 3493.
  */
 static const struct sockaddr *
 normalize_ip_address(const struct sockaddr_storage *ss)
@@ -603,7 +650,7 @@ static inline int estimated_header_overhead(const int af_type)
  */
 int generic_max_transport_msg_size(int sockfd)
 {
-       struct sockaddr_storage ss = {0};
+       struct sockaddr_storage ss = {.ss_family = 0};
        socklen_t sslen = sizeof(ss);
        int af_type = AF_INET;
 
@@ -624,12 +671,12 @@ int generic_max_transport_msg_size(int sockfd)
  * \return A static character string identifying hostname and port of the
  * chosen side in numeric host:port format.
  *
- * \sa getsockname(2), getpeername(2), parse_url(), getnameinfo(3),
+ * \sa getsockname(2), getpeername(2), \ref parse_url(), getnameinfo(3),
  * services(5), nsswitch.conf(5).
  */
 char *remote_name(int fd)
 {
-       struct sockaddr_storage ss = {0};
+       struct sockaddr_storage ss = {.ss_family = 0};
        const struct sockaddr *sa;
        socklen_t sslen = sizeof(ss);
        char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
@@ -735,7 +782,7 @@ __must_check int recv_bin_buffer(int fd, char *buf, size_t size)
  *
  * \return The return value of the underlying call to \a recv_bin_buffer().
  *
- * \sa recv_bin_buffer()
+ * \sa \ref recv_bin_buffer()
  */
 int recv_buffer(int fd, char *buf, size_t size)
 {
@@ -818,25 +865,40 @@ int dccp_available_ccids(uint8_t **ccid_array)
        return nccids;
 }
 
-/**
- * Prepare a structure for \p AF_UNIX socket addresses.
- *
- * \param u Pointer to the struct to be prepared.
- * \param name The socket pathname.
+/*
+ * Prepare a structure for AF_UNIX socket addresses.
  *
- * This just copies \a name to the sun_path component of \a u.
+ * This just copies name to the sun_path component of u, prepending a zero byte
+ * if abstract sockets are supported.
  *
- * \return Positive on success, \p -E_NAME_TOO_LONG if \a name is longer
- * than \p UNIX_PATH_MAX.
+ * The first call to this function tries to bind a socket to the abstract name
+ * space. The result of this test is stored in a static variable. Subsequent
+ * calls read this variable and create abstract sockets on systems that support
+ * them. If a NULL pointer is passed as the name, the function only
+ * initializes the static variable.
  */
-static int init_unix_addr(struct sockaddr_un *u, const char *name,
-               bool abstract)
+static int init_unix_addr(struct sockaddr_un *u, const char *name)
 {
-       if (strlen(name) + abstract >= UNIX_PATH_MAX)
-               return -E_NAME_TOO_LONG;
+       static int use_abstract;
+
        memset(u->sun_path, 0, UNIX_PATH_MAX);
        u->sun_family = PF_UNIX;
-       strcpy(u->sun_path + abstract, name);
+       if (use_abstract == 0) { /* executed only once */
+               int fd = socket(PF_UNIX, SOCK_STREAM, 0);
+               memcpy(u->sun_path, "\0x\0", 3);
+               if (bind(fd, (struct sockaddr *)u, sizeof(*u)) == 0)
+                       use_abstract = 1; /* yes */
+               else
+                       use_abstract = -1; /* no */
+               close(fd);
+               PARA_NOTICE_LOG("%susing abstract socket namespace\n",
+                       use_abstract == 1? "" : "not ");
+       }
+       if (!name)
+               return 0;
+       if (strlen(name) + 1 >= UNIX_PATH_MAX)
+               return -E_NAME_TOO_LONG;
+       strcpy(u->sun_path + (use_abstract == 1? 1 : 0), name);
        return 1;
 }
 
@@ -844,29 +906,23 @@ static int init_unix_addr(struct sockaddr_un *u, const char *name,
  * Create a socket for local communication and listen on it.
  *
  * \param name The socket pathname.
- * \param mode The desired permissions of the socket.
  *
  * This function creates a passive local socket for sequenced, reliable,
  * two-way, connection-based byte streams. The socket file descriptor is set to
  * nonblocking mode and listen(2) is called to prepare the socket for
  * accepting incoming connection requests.
  *
- * If mode is zero, an abstract socket (a non-portable Linux extension) is
- * created. In this case the socket name has no connection with filesystem
- * pathnames.
- *
  * \return The file descriptor on success, negative error code on failure.
  *
  * \sa socket(2), \sa bind(2), \sa chmod(2), listen(2), unix(7).
  */
-int create_local_socket(const char *name, mode_t mode)
+int create_local_socket(const char *name)
 {
        struct sockaddr_un unix_addr;
        int fd, ret;
-       bool abstract = mode == 0;
 
-       ret = init_unix_addr(&unix_addr, name, abstract);
-       if (ret < 0)
+       ret = init_unix_addr(&unix_addr, name);
+       if (ret <= 0) /* error, or name was NULL */
                return ret;
        ret = socket(PF_UNIX, SOCK_STREAM, 0);
        if (ret < 0)
@@ -880,7 +936,9 @@ int create_local_socket(const char *name, mode_t mode)
                ret = -ERRNO_TO_PARA_ERROR(errno);
                goto err;
        }
-       if (!abstract) {
+       if (unix_addr.sun_path[0] != 0) { /* pathname socket */
+               mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
+                       | S_IROTH | S_IWOTH;
                ret = -E_CHMOD;
                if (chmod(name, mode) < 0)
                        goto err;
@@ -906,7 +964,7 @@ err:
  * \return The file descriptor of the connected socket on success, negative on
  * errors.
  *
- * \sa create_local_socket(), unix(7), connect(2).
+ * \sa \ref create_local_socket(), unix(7), connect(2).
  */
 int connect_local_socket(const char *name)
 {
@@ -917,14 +975,7 @@ int connect_local_socket(const char *name)
        fd = socket(PF_UNIX, SOCK_STREAM, 0);
        if (fd < 0)
                return -ERRNO_TO_PARA_ERROR(errno);
-       /* first try (linux-only) abstract socket */
-       ret = init_unix_addr(&unix_addr, name, true);
-       if (ret < 0)
-               goto err;
-       if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) != -1)
-               return fd;
-       /* next try pathname socket */
-       ret = init_unix_addr(&unix_addr, name, false);
+       ret = init_unix_addr(&unix_addr, name);
        if (ret < 0)
                goto err;
        if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) != -1)
@@ -955,8 +1006,7 @@ int recv_cred_buffer(int fd, char *buf, size_t size)
  * \return On success, this call returns the number of bytes sent. On errors,
  * \p -E_SENDMSG is returned.
  *
- * \sa \ref recv_cred_buffer, sendmsg(2), socket(7), unix(7), okir's Black Hats
- * Manual.
+ * \sa \ref recv_cred_buffer, sendmsg(2), socket(7), unix(7).
  */
 ssize_t send_cred_buffer(int sock, char *buf)
 {
diff --git a/net.h b/net.h
index b2bb47c9b40b43fe2eb7c8813e6ffa7364b0853e..2256f376497b89d3382e2bf80ce09725ef64ab02 100644 (file)
--- a/net.h
+++ b/net.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 /** \file net.h exported symbols from net.c */
 
 /**
@@ -52,6 +48,9 @@
 #define DCCP_SOCKOPT_TX_CCID 14 /**< Set/get the TX CCID. */
 #endif
 
+/** The maximum length of the host component in an URL. */
+#define MAX_HOSTLEN 256
+
 /**
  * Flowopts: Transport-layer independent encapsulation of socket options
  *           that need to be registered prior to setting up a connection.
@@ -72,6 +71,7 @@ extern char *parse_cidr(const char *cidr,
                        char *addr, ssize_t addrlen, int32_t *netmask);
 extern char *parse_url(const char *url,
                       char *host, ssize_t hostlen, int32_t *port);
+char *format_url(const char *url, int default_port);
 extern const char *stringify_port(int port, const char *transport);
 /**
  * Ensure that string conforms to the IPv4 address format.
@@ -93,7 +93,7 @@ _static_inline_ bool is_valid_ipv4_address(const char *address)
  * \param address The address string to check.
  *
  * \return 1 if string has a valid IPv6 address syntax, 0 if not.
- * \sa RFC 4291
+ * \sa RFC 4291.
  */
 _static_inline_ bool is_valid_ipv6_address(const char *address)
 {
@@ -128,12 +128,9 @@ bool sockaddr_equal(const struct sockaddr *sa1, const struct sockaddr *sa2);
  */
 /** How many pending connections queue of a listening server will hold. */
 #define BACKLOG        10
-extern int para_listen(unsigned l4type, uint16_t port, struct flowopts *fo);
 
-static inline int para_listen_simple(unsigned l4type, uint16_t port)
-{
-       return para_listen(l4type, port, NULL);
-}
+int para_listen(unsigned l4type, const char *addr, uint16_t port);
+int para_listen_simple(unsigned l4type, uint16_t port);
 
 /** Pretty-printing of IPv4/6 socket addresses */
 extern char *remote_name(int sockfd);
@@ -147,7 +144,7 @@ int recv_bin_buffer(int fd, char *buf, size_t size);
 int recv_buffer(int fd, char *buf, size_t size);
 
 int para_accept(int fd, fd_set *rfds, void *addr, socklen_t size, int *new_fd);
-int create_local_socket(const char *name, mode_t mode);
+int create_local_socket(const char *name);
 int connect_local_socket(const char *name);
 int recv_cred_buffer(int, char *, size_t);
 ssize_t send_cred_buffer(int, char*);
index 2ddf0ee35818fc7fe7aa59ccaf04ad75e7a07502..5da339fe162ecec96d21cfa05fbde48b51f2a307 100644 (file)
--- a/ogg_afh.c
+++ b/ogg_afh.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file ogg_afh.c Audio format handler for ogg/vorbis files. */
 
@@ -66,45 +62,24 @@ static int ogg_vorbis_get_file_info(char *map, size_t numbytes, __a_unused int f
 {
        int ret;
        struct private_vorbis_data pvd;
-       struct ogg_afh_callback_info vorbis_callback_info = {
+       struct oac_callback_info vorbis_callback_info = {
                .packet_callback = vorbis_packet_callback,
                .private_data = &pvd,
        };
 
        vorbis_info_init(&pvd.vi);
        vorbis_comment_init(&pvd.vc);
-       ret = ogg_get_file_info(map, numbytes, afhi, &vorbis_callback_info);
+       ret = oac_get_file_info(map, numbytes, afhi, &vorbis_callback_info);
        vorbis_info_clear(&pvd.vi);
        vorbis_comment_clear(&pvd.vc);
        return ret;
 }
 
-struct vorbis_get_header_data {
-       ogg_stream_state os;
-       char *buf;
-       size_t len;
-};
-
-static void add_ogg_page(ogg_page *og, struct vorbis_get_header_data *vghd)
-{
-       size_t old_len = vghd->len;
-       size_t new_len = vghd->len + og->header_len + og->body_len;
-       char *buf = para_realloc(vghd->buf, new_len), *p = buf + old_len;
-
-       memcpy(p, og->header, og->header_len);
-       memcpy(p + og->header_len, og->body, og->body_len);
-       vghd->buf = buf;
-       vghd->len = new_len;
-       PARA_DEBUG_LOG("header/body/old/new: %li/%li/%zu/%zu\n",
-               og->header_len, og->body_len, old_len, new_len);
-}
-
 static int vorbis_get_header_callback(ogg_packet *packet, int packet_num,
                int serial, __a_unused struct afh_info *afhi, void *private_data)
 {
        int ret;
-       struct vorbis_get_header_data *vghd = private_data;
-       ogg_page og;
+       struct oac_custom_header *h = private_data;
        static unsigned char dummy_packet[] = {
                0x03,
                'v', 'o', 'r', 'b', 'i', 's',
@@ -115,17 +90,12 @@ static int vorbis_get_header_callback(ogg_packet *packet, int packet_num,
        };
 
        PARA_DEBUG_LOG("processing ogg packet #%d\n", packet_num);
-       if (packet_num > 2)
-               return 0;
        if (packet_num == 0) {
-               ogg_stream_init(&vghd->os, serial);
-               ret = ogg_stream_packetin(&vghd->os, packet);
+               oac_custom_header_init(serial, h);
+               ret = oac_custom_header_append(packet, h);
                if (ret < 0)
-                       goto out;
-               ret = -E_OGG_STREAM_FLUSH;
-               if (ogg_stream_flush(&vghd->os, &og) == 0)
-                       goto out;
-               add_ogg_page(&og, vghd);
+                       return ret;
+               oac_custom_header_flush(h);
                return 1;
        }
        if (packet_num == 1) {
@@ -133,42 +103,37 @@ static int vorbis_get_header_callback(ogg_packet *packet, int packet_num,
                PARA_INFO_LOG("replacing metadata packet\n");
                replacement.packet = dummy_packet;
                replacement.bytes = sizeof(dummy_packet);
-               ret = ogg_stream_packetin(&vghd->os, &replacement);
-               if (ret >= 0)
-                       return 1;
-               ret = -E_OGG_PACKET_IN;
-               goto out;
+               ret = oac_custom_header_append(&replacement, h);
+               return ret < 0? ret : 1;
        }
-       ret = -E_OGG_PACKET_IN;
-       if (ogg_stream_packetin(&vghd->os, packet) < 0)
-               goto out;
-       while (ogg_stream_flush(&vghd->os, &og))
-               add_ogg_page(&og, vghd);
-       ret = 0;
-out:
-       ogg_stream_clear(&vghd->os);
-       return ret;
+       assert(packet_num == 2);
+       ret = oac_custom_header_append(packet, h);
+       if (ret < 0)
+               return ret;
+       oac_custom_header_flush(h);
+       return 0;
 }
 
 static void vorbis_get_header(void *map, size_t mapsize, char **buf,
                size_t *len)
 {
        int ret;
-       struct vorbis_get_header_data vghd = {.len = 0};
-       struct ogg_afh_callback_info cb = {
+       struct oac_custom_header *h = oac_custom_header_new();
+       struct oac_callback_info cb = {
                .packet_callback = vorbis_get_header_callback,
-               .private_data = &vghd,
+               .private_data = h,
        };
 
-       ret = ogg_get_file_info(map, mapsize, NULL, &cb);
-       if (ret < 0)
-               goto fail;
-       *buf = vghd.buf;
-       *len = vghd.len;
-       PARA_INFO_LOG("created %zu byte ogg vorbis header\n", *len);
-       return;
-fail:
-       PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       ret = oac_get_file_info(map, mapsize, NULL, &cb);
+       *len = oac_custom_header_get(buf, h);
+       if (ret < 0) {
+               PARA_ERROR_LOG("could not create ogg/vorbis header: %s\n",
+                       para_strerror(-ret));
+               free(*buf);
+               *buf = NULL;
+               *len = 0;
+       } else
+               PARA_INFO_LOG("created %zu byte ogg vorbis header\n", *len);
 }
 
 static int vorbis_make_meta_packet(struct taginfo *tags, ogg_packet *result)
@@ -199,7 +164,7 @@ static int vorbis_rewrite_tags(const char *map, size_t mapsize,
        ret = vorbis_make_meta_packet(tags, &packet);
        if (ret < 0)
                return ret;
-       ret = ogg_rewrite_tags(map, mapsize, output_fd, (char *)packet.packet,
+       ret = oac_rewrite_tags(map, mapsize, output_fd, (char *)packet.packet,
                packet.bytes);
        free(packet.packet);
        return ret;
@@ -208,14 +173,15 @@ static int vorbis_rewrite_tags(const char *map, size_t mapsize,
 static const char * const ogg_suffixes[] = {"ogg", NULL};
 
 /**
- * The init function of the ogg vorbis audio format handler.
+ * The ogg vorbis audio format handler.
  *
- * \param afh Pointer to the struct to initialize.
+ * It should actually be called vorbis because the ogg container format is also
+ * employed for the speex and flac codecs. Both the ogg and the vorbis
+ * libraries must be installed to get this audio format handler.
  */
-void ogg_init(struct audio_format_handler *afh)
-{
-       afh->get_file_info = ogg_vorbis_get_file_info;
-       afh->get_header = vorbis_get_header;
-       afh->suffixes = ogg_suffixes;
-       afh->rewrite_tags = vorbis_rewrite_tags;
-}
+const struct audio_format_handler ogg_afh = {
+       .get_file_info = ogg_vorbis_get_file_info,
+       .get_header = vorbis_get_header,
+       .suffixes = ogg_suffixes,
+       .rewrite_tags = vorbis_rewrite_tags
+};
index adab7f481fbb0e7873c1d1e1ac4968834bd81fe3..3e36bdd5b1e9d3615695f975541bf69c0cc05093 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file ogg_afh_common.c Functions common to all ogg/ codecs. */
 
@@ -19,7 +15,7 @@
 /* Taken from decoder_example.c of libvorbis-1.2.3. */
 static int process_packets_2_and_3(ogg_sync_state *oss,
                ogg_stream_state *stream, struct afh_info *afhi,
-               struct ogg_afh_callback_info *ci)
+               struct oac_callback_info *ci)
 {
        ogg_page page;
        ogg_packet packet;
@@ -60,7 +56,7 @@ static int process_packets_2_and_3(ogg_sync_state *oss,
 }
 
 static int process_ogg_packets(ogg_sync_state *oss, struct afh_info *afhi,
-               struct ogg_afh_callback_info *ci)
+               struct oac_callback_info *ci)
 {
        ogg_packet packet;
        ogg_stream_state stream;
@@ -122,24 +118,24 @@ static void set_chunk_tv(int frames_per_chunk, int frequency,
  *
  * \return Standard.
  */
-int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
-               struct ogg_afh_callback_info *ci)
+int oac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
+               struct oac_callback_info *ci)
 {
        ogg_sync_state oss;
        ogg_page op;
-       long len = numbytes;
        char *buf;
-       int ret, i, j, frames_per_chunk, ct_size;
-       long long unsigned num_frames = 0;
+       int ret, i, j, frames_per_chunk, ct_size, prev_pageno = 0;
+       long long unsigned granule_skip = 0, num_frames = 0;
+       int64_t granule = 0, prev_granule = 0;
 
        ogg_sync_init(&oss);
        ret = -E_OGG_SYNC;
-       buf = ogg_sync_buffer(&oss, len);
+       buf = ogg_sync_buffer(&oss, numbytes);
        if (!buf)
                goto out;
-       memcpy(buf, map, len);
+       memcpy(buf, map, numbytes);
        ret = -E_OGG_SYNC;
-       if (ogg_sync_wrote(&oss, len) < 0)
+       if (ogg_sync_wrote(&oss, numbytes) < 0)
                goto out;
        ret = process_ogg_packets(&oss, afhi, ci);
        if (ret < 0)
@@ -150,8 +146,17 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
        oss.returned = 0;
        oss.fill = numbytes;
        /* count ogg pages and get duration of the file */
-       for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++)
-               num_frames = ogg_page_granulepos(&op);
+       for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++) {
+               int this_pageno = ogg_page_pageno(&op);
+               int64_t this_granule = ogg_page_granulepos(&op);
+               if (this_granule >= 0)
+                       granule = this_granule;
+               if (i > 0 && this_pageno != prev_pageno + 1) /* hole */
+                       granule_skip += granule - prev_granule;
+               prev_pageno = this_pageno;
+               prev_granule = granule;
+       }
+       num_frames = granule - granule_skip;
        PARA_INFO_LOG("%d pages, %llu frames\n", i, num_frames);
        ret = -E_OGG_EMPTY;
        if (i == 0)
@@ -159,7 +164,7 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
        afhi->seconds_total = num_frames / afhi->frequency;
        /* use roughly one page per chunk */
        frames_per_chunk = num_frames / i;
-       PARA_INFO_LOG("%" PRIu32 "seconds, %d frames/chunk\n",
+       PARA_INFO_LOG("%" PRIu32 " seconds, %d frames/chunk\n",
                afhi->seconds_total, frames_per_chunk);
        ct_size = 250;
        afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
@@ -168,7 +173,7 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
        oss.returned = afhi->header_len;
        oss.fill = numbytes;
        for (j = 1; ogg_sync_pageseek(&oss, &op) > 0; /* nothing */) {
-               int granule = ogg_page_granulepos(&op);
+               granule = ogg_page_granulepos(&op);
 
                while (granule >= (j + 1) * frames_per_chunk) {
                        j++;
@@ -182,6 +187,7 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
                }
        }
        afhi->chunks_total = j;
+       set_max_chunk_size(afhi);
        set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
        ret = 0;
 out:
@@ -216,7 +222,7 @@ static int write_ogg_page(int fd, const ogg_page *op)
  *
  * \return Standard.
  */
-int ogg_rewrite_tags(const char *map, size_t map_sz, int fd,
+int oac_rewrite_tags(const char *map, size_t map_sz, int fd,
                char *meta_packet, size_t meta_sz)
 {
        ogg_sync_state oss_in, oss_out;
@@ -332,3 +338,108 @@ out:
                ogg_stream_clear(so);
        return ret;
 }
+
+/* Structure for providing custom headers for streaming. */
+struct oac_custom_header {
+       char *buf;
+       size_t len;
+       ogg_stream_state oss;
+};
+
+/**
+ * Allocate and return a custom header structure.
+ *
+ * For some audio codecs which employ the ogg container format, the server side
+ * wants to replace the meta tags at the beginning of the file because they are
+ * not needed for streaming and can be arbitrary large. The structure returned
+ * by this function is typically used as the ->private field of struct \ref
+ * oac_callback_info for \ref oac_get_file_info(). This allows the audio format
+ * handler to set up a custom header which is identical to the original header,
+ * but with the meta data part replaced by fixed length dummy contents.
+ *
+ * \return The returned memory must be initialized with the serial number of
+ * the ogg stream before ogg packets can be submitted to it. This is not done
+ * here because the header structure is allocated before \ref
+ * oac_get_file_info() is called, and the serial number is not known at this
+ * point.
+ *
+ * \sa \ref oac_custom_header_init().
+ */
+struct oac_custom_header *oac_custom_header_new(void)
+{
+       return para_calloc(sizeof(struct oac_custom_header));
+}
+
+/**
+ * Set the serial number of an allocated header structure.
+ *
+ * \param serial Passed to the callback function.
+ * \param h As returned from \ref oac_custom_header_new().
+ *
+ * This function must be called before any packets are submitted.
+ */
+void oac_custom_header_init(int serial, struct oac_custom_header *h)
+{
+       ogg_stream_init(&h->oss, serial);
+}
+
+/**
+ * Submit an ogg packet to a custom header structure.
+ *
+ * \param op The packet to append.
+ * \param h Must be initialized.
+ *
+ * The packet may be the one which was passed to the callback, or a completely
+ * different one, like a dummy metadata packet.
+ *
+ * \return Standard.
+ */
+int oac_custom_header_append(ogg_packet *op, struct oac_custom_header *h)
+{
+       return ogg_stream_packetin(&h->oss, op) < 0? -E_OGG_PACKET_IN : 1;
+}
+
+/**
+ * Force remaining packets into an ogg page.
+ *
+ * \param h Should contain submitted but not yet flushed packets.
+ *
+ * This is called after the first packet has been submitted with \ref
+ * oac_custom_header_append() to make sure the first ogg page contains only
+ * this packet. Also when header processing is complete, the callbacks call
+ * this to force the previously submitted packets into a page.
+ */
+void oac_custom_header_flush(struct oac_custom_header *h)
+{
+       ogg_page og;
+
+       while (ogg_stream_flush(&h->oss, &og)) {
+               size_t len = og.header_len + og.body_len;
+               h->buf = para_realloc(h->buf, h->len + len);
+               memcpy(h->buf + h->len, og.header, og.header_len);
+               memcpy(h->buf + h->len + og.header_len, og.body, og.body_len);
+               h->len += len;
+       }
+}
+
+/**
+ * Return the custom header buffer and deallocate resources.
+ *
+ * This is called after the ogg packets which comprise the header have been
+ * submitted and flushed.
+ *
+ * \param buf Result pointer.
+ * \param h Must not be used any more after the call.
+ *
+ * \return The size of the header. This is the sum of the sizes of all ogg
+ * pages that have been flushed out.
+ */
+size_t oac_custom_header_get(char **buf, struct oac_custom_header *h)
+{
+       size_t ret = h->len;
+
+       *buf = h->buf;
+       ogg_stream_clear(&h->oss);
+       free(h);
+       return ret;
+}
index 7b9d1313af4816b79d1177c7898b94a70ee24775..e0cf2d40c3ffb825c9f19338aa792558cb1c8742 100644 (file)
@@ -1,14 +1,16 @@
-/*
- * Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /**
  * \file ogg_afh_common.h Structures and prototypes common to audio format
  * handlers that use the ogg container format.
  */
 
+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);
+size_t oac_custom_header_get(char **buf, struct oac_custom_header *h);
+
 /**
  * Callback structure provided by audio format handlers.
  *
@@ -16,7 +18,7 @@
  * function whose purpose is to extract the meta information about the audio
  * file from the first few ogg packets of the bitstream.
  */
-struct ogg_afh_callback_info {
+struct oac_callback_info {
        /**
         * ogg_get_file_info() calls this function for the first three ogg
         * packets, or until the callback returns a non-positive value. If it
@@ -33,7 +35,7 @@ struct ogg_afh_callback_info {
        void *private_data;
 };
 
-int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
-               struct ogg_afh_callback_info *ci);
-int ogg_rewrite_tags(const char *map, size_t mapsize, int fd,
+int oac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
+               struct oac_callback_info *ci);
+int oac_rewrite_tags(const char *map, size_t mapsize, int fd,
                char *meta_packet, size_t meta_sz);
index 04be7020e57f29351b73501203db6b1bfa1dbb39..708a27e52b68c1c2add01a3b3fe39f721485e39f 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file oggdec_filter.c Paraslash's ogg vorbis decoder. */
 
@@ -12,7 +8,6 @@
 #include "para.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "error.h"
@@ -264,16 +259,10 @@ out:
        return ret;
 }
 
-/**
- * The init function of the ogg vorbis decoder.
- *
- * \param f Its fields are filled in by the function.
- */
-void oggdec_filter_init(struct filter *f)
-{
-       f->open = ogg_open;
-       f->close = ogg_close;
-       f->pre_select = ogg_pre_select;
-       f->post_select = ogg_post_select;
-       f->execute = oggdec_execute;
-}
+const struct filter lsg_filter_cmd_com_oggdec_user_data = {
+       .open = ogg_open,
+       .close = ogg_close,
+       .pre_select = ogg_pre_select,
+       .post_select = ogg_post_select,
+       .execute = oggdec_execute
+};
diff --git a/openssl.c b/openssl.c
new file mode 100644 (file)
index 0000000..0ad9d7d
--- /dev/null
+++ b/openssl.c
@@ -0,0 +1,416 @@
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file openssl.c Openssl-based encryption/decryption routines. */
+
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/sha.h>
+#include <openssl/bn.h>
+#include <openssl/aes.h>
+
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "crypt.h"
+#include "crypt_backend.h"
+#include "portable_io.h"
+
+struct asymmetric_key {
+       RSA *rsa;
+};
+
+void get_random_bytes_or_die(unsigned char *buf, int num)
+{
+       unsigned long err;
+
+       /* RAND_bytes() returns 1 on success, 0 otherwise. */
+       if (RAND_bytes(buf, num) == 1)
+               return;
+       err = ERR_get_error();
+       PARA_EMERG_LOG("%s\n", ERR_reason_error_string(err));
+       exit(EXIT_FAILURE);
+}
+
+/*
+ * 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().
+ */
+void crypt_init(void)
+{
+       int seed, ret = RAND_load_file("/dev/urandom", 64);
+
+       if (ret != 64) {
+               PARA_EMERG_LOG("could not seed PRNG (ret = %d)\n", ret);
+               exit(EXIT_FAILURE);
+       }
+       get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed));
+       srandom(seed);
+}
+
+void crypt_shutdown(void)
+{
+#ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA
+       CRYPTO_cleanup_all_ex_data();
+#endif
+#ifdef HAVE_OPENSSL_THREAD_STOP /* openssl-1.1 or later */
+       OPENSSL_thread_stop();
+#else /* openssl-1.0 */
+       ERR_remove_thread_state(NULL);
+#endif
+       EVP_cleanup();
+}
+
+/*
+ * The public key loading functions below were inspired by corresponding code
+ * of openssh-5.2p1, Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo,
+ * Finland. However, not much of the original code remains.
+ */
+
+static int read_bignum(const unsigned char *buf, size_t len, BIGNUM **result)
+{
+       const unsigned char *p = buf, *end = buf + len;
+       uint32_t bnsize;
+       BIGNUM *bn;
+
+       if (p + 4 < p)
+               return -E_BIGNUM;
+       if (p + 4 > end)
+               return -E_BIGNUM;
+       bnsize = read_u32_be(p);
+       PARA_DEBUG_LOG("bnsize: %u\n", bnsize);
+       p += 4;
+       if (p + bnsize < p)
+               return -E_BIGNUM;
+       if (p + bnsize > end)
+               return -E_BIGNUM;
+       if (bnsize > 8192)
+               return -E_BIGNUM;
+       bn = BN_bin2bn(p, bnsize, NULL);
+       if (!bn)
+               return -E_BIGNUM;
+       *result = bn;
+       return bnsize + 4;
+}
+
+static int read_rsa_bignums(const unsigned char *blob, int blen, RSA **result)
+{
+       int ret;
+       RSA *rsa;
+       BIGNUM *n, *e;
+       const unsigned char *p = blob, *end = blob + blen;
+
+       rsa = RSA_new();
+       if (!rsa)
+               return -E_BIGNUM;
+       ret = read_bignum(p, end - p, &e);
+       if (ret < 0)
+               goto free_rsa;
+       p += ret;
+       ret = read_bignum(p, end - p, &n);
+       if (ret < 0)
+               goto free_e;
+#ifdef HAVE_RSA_SET0_KEY
+       RSA_set0_key(rsa, n, e, NULL);
+#else
+       rsa->n = n;
+       rsa->e = e;
+#endif
+       *result = rsa;
+       return 1;
+free_e:
+       BN_free(e);
+free_rsa:
+       RSA_free(rsa);
+       return ret;
+}
+
+static int read_pem_private_key(const char *path, RSA **rsa)
+{
+       EVP_PKEY *pkey;
+       BIO *bio = BIO_new(BIO_s_file());
+
+       *rsa = NULL;
+       if (!bio)
+               return -E_PRIVATE_KEY;
+       if (BIO_read_filename(bio, path) <= 0)
+               goto bio_free;
+       pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
+       if (!pkey)
+               goto bio_free;
+       *rsa = EVP_PKEY_get1_RSA(pkey);
+       EVP_PKEY_free(pkey);
+bio_free:
+       BIO_free(bio);
+       return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY;
+}
+
+static int read_private_rsa_params(const unsigned char *blob,
+               const unsigned char *end, RSA **result)
+{
+       int ret;
+       RSA *rsa;
+       BN_CTX *ctx;
+       BIGNUM *n, *e, *d, *iqmp, *p, *q; /* stored in the key file */
+       BIGNUM *dmp1, *dmq1; /* these will be computed */
+       BIGNUM *tmp;
+       const unsigned char *cp = blob;
+
+       rsa = RSA_new();
+       if (!rsa)
+               return -E_BIGNUM;
+       ret = -E_BIGNUM;
+       tmp = BN_new();
+       if (!tmp)
+               goto free_rsa;
+       ctx = BN_CTX_new();
+       if (!ctx)
+               goto free_tmp;
+       dmp1 = BN_new();
+       if (!dmp1)
+               goto free_ctx;
+       dmq1 = BN_new();
+       if (!dmq1)
+               goto free_dmp1;
+       ret = read_bignum(cp, end - cp, &n);
+       if (ret < 0)
+               goto free_dmq1;
+       cp += ret;
+       ret = read_bignum(cp, end - cp, &e);
+       if (ret < 0)
+               goto free_n;
+       cp += ret;
+       ret = read_bignum(cp, end - cp, &d);
+       if (ret < 0)
+               goto free_e;
+       cp += ret;
+       ret = read_bignum(cp, end - cp, &iqmp);
+       if (ret < 0)
+               goto free_d;
+       cp += ret;
+       ret = read_bignum(cp, end - cp, &p);
+       if (ret < 0)
+               goto free_iqmp;
+       cp += ret;
+       ret = read_bignum(cp, end - cp, &q);
+       if (ret < 0)
+               goto free_p;
+       ret = -E_BIGNUM;
+       if (!BN_sub(tmp, q, BN_value_one()))
+               goto free_q;
+       if (!BN_mod(dmp1, d, tmp, ctx))
+               goto free_q;
+       if (!BN_sub(tmp, q, BN_value_one()))
+               goto free_q;
+       if (!BN_mod(dmq1, d, tmp, ctx))
+               goto free_q;
+#ifdef HAVE_RSA_SET0_KEY
+       RSA_set0_key(rsa, n, e, d);
+       RSA_set0_factors(rsa, p, q);
+       RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);
+#else
+       rsa->n = n;
+       rsa->e = e;
+       rsa->d = d;
+       rsa->p = p;
+       rsa->q = q;
+       rsa->dmp1 = dmp1;
+       rsa->dmq1 = dmq1;
+       rsa->iqmp = iqmp;
+#endif
+       *result = rsa;
+       ret = 1;
+       goto free_ctx;
+free_q:
+       BN_clear_free(q);
+free_p:
+       BN_clear_free(p);
+free_iqmp:
+       BN_clear_free(iqmp);
+free_d:
+       BN_clear_free(d);
+free_e:
+       BN_free(e);
+free_n:
+       BN_free(n);
+free_dmq1:
+       BN_clear_free(dmq1);
+free_dmp1:
+       BN_clear_free(dmp1);
+free_ctx:
+       BN_CTX_free(ctx);
+free_tmp:
+       BN_clear_free(tmp);
+free_rsa:
+       if (ret < 0)
+               RSA_free(rsa);
+       return ret;
+}
+
+static int get_private_key(const char *path, RSA **rsa)
+{
+       int ret;
+       unsigned char *blob, *end;
+       size_t blob_size;
+
+       *rsa = NULL;
+       ret = decode_private_key(path, &blob, &blob_size);
+       if (ret < 0)
+               return ret;
+       end = blob + blob_size;
+       if (ret == PKT_OPENSSH) {
+               ret = find_openssh_bignum_offset(blob, blob_size);
+               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);
+       } else
+               ret = read_pem_private_key(path, rsa);
+free_blob:
+       free(blob);
+       return ret;
+}
+
+int apc_get_pubkey(const char *key_file, struct asymmetric_key **result)
+{
+       unsigned char *blob;
+       size_t decoded_size;
+       int ret;
+       struct asymmetric_key *key = para_malloc(sizeof(*key));
+
+       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);
+       if (ret < 0)
+               goto free_blob;
+       ret = RSA_size(key->rsa);
+       assert(ret > 0);
+       *result = key;
+free_blob:
+       free(blob);
+out:
+       if (ret < 0) {
+               free(key);
+               *result = NULL;
+               PARA_ERROR_LOG("can not load key %s\n", key_file);
+       }
+       return ret;
+}
+
+void apc_free_pubkey(struct asymmetric_key *key)
+{
+       if (!key)
+               return;
+       RSA_free(key->rsa);
+       free(key);
+}
+
+int apc_priv_decrypt(const char *key_file, unsigned char *outbuf,
+               unsigned char *inbuf, int inlen)
+{
+       struct asymmetric_key *priv;
+       int ret;
+
+       ret = check_private_key_file(key_file);
+       if (ret < 0)
+               return ret;
+       if (inlen < 0)
+               return -E_RSA;
+       priv = para_malloc(sizeof(*priv));
+       ret = get_private_key(key_file, &priv->rsa);
+       if (ret < 0) {
+               free(priv);
+               return ret;
+       }
+       /*
+        * RSA is vulnerable to timing attacks. Generate a random blinding
+        * factor to protect against this kind of attack.
+        */
+       ret = -E_BLINDING;
+       if (RSA_blinding_on(priv->rsa, NULL) == 0)
+               goto out;
+       ret = RSA_private_decrypt(inlen, inbuf, outbuf, priv->rsa,
+               RSA_PKCS1_OAEP_PADDING);
+       RSA_blinding_off(priv->rsa);
+       if (ret <= 0)
+               ret = -E_DECRYPT;
+out:
+       RSA_free(priv->rsa);
+       free(priv);
+       return ret;
+}
+
+int apc_pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf,
+               unsigned len, unsigned char *outbuf)
+{
+       int ret, flen = len; /* RSA_public_encrypt expects a signed int */
+
+       if (flen < 0)
+               return -E_ENCRYPT;
+       ret = RSA_public_encrypt(flen, inbuf, outbuf, pub->rsa,
+               RSA_PKCS1_OAEP_PADDING);
+       return ret < 0? -E_ENCRYPT : ret;
+}
+
+struct stream_cipher {
+       EVP_CIPHER_CTX *aes;
+};
+
+struct stream_cipher *sc_new(const unsigned char *data, int len)
+{
+       struct stream_cipher *sc = para_malloc(sizeof(*sc));
+
+       assert(len >= 2 * AES_CRT128_BLOCK_SIZE);
+       sc->aes = EVP_CIPHER_CTX_new();
+       EVP_EncryptInit_ex(sc->aes, EVP_aes_128_ctr(), NULL, data,
+               data + AES_CRT128_BLOCK_SIZE);
+       return sc;
+}
+
+void sc_free(struct stream_cipher *sc)
+{
+       if (!sc)
+               return;
+       EVP_CIPHER_CTX_free(sc->aes);
+       free(sc);
+}
+
+static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src,
+               struct iovec *dst)
+{
+       int ret, inlen = src->iov_len, outlen, tmplen;
+
+       *dst = (typeof(*dst)) {
+               /* Add one for the terminating zero byte. */
+               .iov_base = para_malloc(inlen + 1),
+               .iov_len = inlen
+       };
+       ret = EVP_EncryptUpdate(ctx, dst->iov_base, &outlen, src->iov_base, inlen);
+       assert(ret != 0);
+       ret = EVP_EncryptFinal_ex(ctx, dst->iov_base + outlen, &tmplen);
+       assert(ret != 0);
+       outlen += tmplen;
+       ((char *)dst->iov_base)[outlen] = '\0';
+       dst->iov_len = outlen;
+}
+
+void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
+{
+       return aes_ctr128_crypt(sc->aes, src, dst);
+}
+
+void hash_function(const char *data, unsigned long len, unsigned char *hash)
+{
+       SHA_CTX c;
+       SHA1_Init(&c);
+       SHA1_Update(&c, data, len);
+       SHA1_Final(hash, &c);
+}
index 64eeb03c1a4bd89bb64488495ed5d76c3ca322e9..dca6cfbad1ce3ff8cdf6d820b01bf33bf9a02110 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file opus_afh.c Audio format handler for ogg/opus files. */
 
@@ -84,6 +80,14 @@ static int opus_get_comments(char *comments, int length,
        return 1;
 }
 
+/*
+ * Ogg/Opus has two mandatory header packets:
+ *
+ * 1. ID header (identifies the stream as Opus). Dedicated "BOS" ogg page.
+ * 2. Comment header (metadata). May span multiple pages.
+ *
+ * See doc/draft-ietf-codec-oggopus.xml in the opus source tree for details.
+ */
 static int opus_packet_callback(ogg_packet *packet, int packet_num,
                __a_unused int serial, struct afh_info *afhi,
                void *private_data)
@@ -123,11 +127,11 @@ static int opus_get_file_info(char *map, size_t numbytes, __a_unused int fd,
        int ret, ms;
        struct opus_header oh = {.version = 0};
 
-       struct ogg_afh_callback_info opus_callback_info = {
+       struct oac_callback_info opus_callback_info = {
                .packet_callback = opus_packet_callback,
                .private_data = &oh,
        };
-       ret = ogg_get_file_info(map, numbytes, afhi, &opus_callback_info);
+       ret = oac_get_file_info(map, numbytes, afhi, &opus_callback_info);
        if (ret < 0)
                return ret;
        ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */
@@ -222,19 +226,80 @@ static int opus_rewrite_tags(const char *map, size_t mapsize,
        int ret;
 
        meta_sz = opus_make_meta_packet(tags, &meta_packet);
-       ret = ogg_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz);
+       ret = oac_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz);
        free(meta_packet);
        return ret;
 }
 
-/**
- * The init function of the ogg/opus audio format handler.
- *
- * \param afh Pointer to the struct to initialize.
+/*
+ * See doc/draft-ietf-codec-oggopus.xml in the opus source tree for details
+ * about the format of the comment header.
  */
-void opus_afh_init(struct audio_format_handler *afh)
+static int opus_get_header_callback(ogg_packet *packet, int packet_num,
+               int serial, __a_unused struct afh_info *afhi, void *private_data)
+{
+       struct oac_custom_header *h = private_data;
+       int ret;
+       static unsigned char dummy_tags[] = { /* a minimal comment header */
+               'O', 'p', 'u', 's', 'T', 'a', 'g', 's',
+               0x06, 0x00, 0x00, 0x00, /* vendor string length */
+               'd', 'u', 'm', 'm', 'y', '\0', /* vendor string */
+               0x00, 0x00, 0x00, 0x00, /* user comment list length */
+       };
+       ogg_packet replacement;
+
+       if (packet_num == 0) {
+               oac_custom_header_init(serial, h);
+               ret = oac_custom_header_append(packet, h);
+               if (ret < 0)
+                       return ret;
+               oac_custom_header_flush(h);
+               return 1;
+       }
+       assert(packet_num == 1);
+       PARA_INFO_LOG("replacing metadata packet\n");
+       replacement = *packet;
+       replacement.packet = dummy_tags;
+       replacement.bytes = sizeof(dummy_tags);
+       ret = oac_custom_header_append(&replacement, h);
+       if (ret < 0)
+               return ret;
+       oac_custom_header_flush(h);
+       return 0;
+}
+
+static void opus_get_header(void *map, size_t mapsize, char **buf,
+               size_t *len)
 {
-       afh->get_file_info = opus_get_file_info,
-       afh->suffixes = opus_suffixes;
-       afh->rewrite_tags = opus_rewrite_tags;
+       int ret;
+       struct oac_custom_header *h = oac_custom_header_new();
+       struct oac_callback_info cb = {
+               .packet_callback = opus_get_header_callback,
+               .private_data = h,
+       };
+
+       ret = oac_get_file_info(map, mapsize, NULL, &cb);
+       *len = oac_custom_header_get(buf, h);
+       if (ret < 0) {
+               PARA_ERROR_LOG("could not create custom header: %s\n",
+                       para_strerror(-ret));
+               free(*buf);
+               *buf = NULL;
+               *len = 0;
+       } else
+               PARA_INFO_LOG("created %zu byte ogg/opus header\n", *len);
 }
+
+/**
+ * The audio format handler for ogg/opus.
+ *
+ * The opus codec was standardized by the Internet Engineering Task Force and
+ * is described in RFC 6716 (2012). The audio format handler depends on the ogg
+ * and the opus libraries.
+ */
+const struct audio_format_handler opus_afh = {
+       .get_file_info = opus_get_file_info,
+       .get_header = opus_get_header,
+       .suffixes = opus_suffixes,
+       .rewrite_tags = opus_rewrite_tags,
+};
index 2160f15192f30a8f1b5fde0be38fcd127a89681a..f8cf459c1008fe9731d9eaaaf253fd1be6710af9 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2013 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2013 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /**
  * \file opus_common.h
index 282229855d0d97519f886483c249d8e3f4b00b4f..10ed394d295072d909441fe211fbcdc695ff62d0 100644 (file)
@@ -4,7 +4,7 @@
  * Copyright (c) 2007-2012 Xiph.Org Foundation
  * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
  *
- * Licensed under the GPL v2. For licencing details see COPYING.
+ * Licensed under the GPL v2, see file COPYING.
  */
 
 /** \file opusdec_filter.c The ogg/opus decoder. */
@@ -50,7 +50,6 @@
 #include "para.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "error.h"
@@ -284,18 +283,10 @@ static void opusdec_pre_select(struct sched *s, void *context)
                return sched_min_delay(s);
 }
 
-/**
- * The init function of the opusdec filter.
- *
- * \param f Pointer to the filter struct to initialize.
- *
- * \sa filter::init.
- */
-void opusdec_filter_init(struct filter *f)
-{
-       f->open = opusdec_open;
-       f->close = opusdec_close;
-       f->pre_select = opusdec_pre_select;
-       f->post_select = opusdec_post_select;
-       f->execute = opusdec_execute;
-}
+const struct filter lsg_filter_cmd_com_opusdec_user_data = {
+       .open = opusdec_open,
+       .close = opusdec_close,
+       .pre_select = opusdec_pre_select,
+       .post_select = opusdec_post_select,
+       .execute = opusdec_execute,
+};
index 8e87452b816e68f03f0931e79efe007d7491b6c8..f80301e9df282c13ef0e7d8ab2a025dd61624ba7 100644 (file)
--- a/oss_mix.c
+++ b/oss_mix.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file oss_mix.c The OSS mixer plugin. */
 
@@ -135,19 +131,13 @@ static void oss_mix_close(struct mixer_handle **handle)
        *handle = NULL;
 }
 
-/**
- * The init function of the OSS mixer.
- *
- * \param self The structure to initialize.
- *
- * \sa struct \ref mixer, \ref alsa_mix_init().
- */
-void oss_mix_init(struct mixer *self)
-{
-       self->open = oss_mix_open;
-       self->get_channels = oss_mix_get_channels;
-       self->set_channel = oss_mix_set_channel;
-       self->get = oss_mix_get;
-       self->set = oss_mix_set;
-       self->close = oss_mix_close;
-}
+/** The mixer operations for the OSS mixer. */
+const struct mixer oss_mixer = {
+       .name = "oss",
+       .open = oss_mix_open,
+       .get_channels = oss_mix_get_channels,
+       .set_channel = oss_mix_set_channel,
+       .close = oss_mix_close,
+       .get = oss_mix_get,
+       .set = oss_mix_set
+};
index 20186667e2f383880538275d61d1607203ed2b8a..311a514dc86ff6bfb01abf53d16a75672827af12 100644 (file)
@@ -1,25 +1,20 @@
-/*
- * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file oss_write.c Paraslash's oss output plugin. */
 
 #include <regex.h>
 #include <sys/ioctl.h>
 #include <sys/soundcard.h>
+#include <lopsub.h>
 
+#include "write_cmd.lsg.h"
 #include "para.h"
 #include "fd.h"
 #include "string.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "write.h"
-#include "write_common.h"
-#include "oss_write.cmdline.h"
 #include "error.h"
 
 /** Data specific to the oss writer. */
@@ -62,7 +57,7 @@ static int get_oss_format(enum sample_format sf)
        case SF_S16_BE: return AFMT_S16_BE;
        case SF_U16_LE: return AFMT_U16_LE;
        case SF_U16_BE: return AFMT_U16_BE;
-       default: return AFMT_S16_LE;
+       default: return -E_BAD_SAMPLE_FORMAT;
        }
 }
 
@@ -106,11 +101,11 @@ static int oss_init(struct writer_node *wn, unsigned sample_rate,
 {
        int ret, format;
        unsigned ch, rate;
-       struct oss_write_args_info *conf = wn->conf;
        struct private_oss_write_data *powd = para_calloc(sizeof(*powd));
+       const char *dev = WRITE_CMD_OPT_STRING_VAL(OSS, DEVICE, wn->lpr);
 
-       PARA_INFO_LOG("opening %s\n", conf->device_arg);
-       ret = para_open(conf->device_arg, O_WRONLY, 0);
+       PARA_INFO_LOG("opening %s\n", dev);
+       ret = para_open(dev, O_WRONLY, 0);
        if (ret < 0)
                goto err_free;
        powd->fd = ret;
@@ -118,10 +113,14 @@ static int oss_init(struct writer_node *wn, unsigned sample_rate,
        if (ret < 0)
                goto err;
        /* set PCM format */
-       sample_format = format = get_oss_format(sample_format);
+       ret = get_oss_format(sample_format);
+       if (ret < 0)
+               return ret;
+       sample_format = format = ret;
        ret = ioctl(powd->fd, SNDCTL_DSP_SETFMT, &format);
        if (ret < 0) {
                ret = -ERRNO_TO_PARA_ERROR(errno);
+               PARA_ERROR_LOG("could not set sample format\n");
                goto err;
        }
        ret = -E_BAD_SAMPLE_FORMAT;
@@ -175,8 +174,7 @@ err:
        close(powd->fd);
 err_free:
        free(powd);
-       PARA_ERROR_LOG("failed to init %s: %s\n", conf->device_arg,
-               para_strerror(-ret));
+       PARA_ERROR_LOG("failed to init %s: %s\n", dev, para_strerror(-ret));
        return ret;
 }
 
@@ -240,37 +238,8 @@ out:
        return ret;
 }
 
-__malloc static void *oss_parse_config_or_die(int argc, char **argv)
-{
-       struct oss_write_args_info *conf = para_calloc(sizeof(*conf));
-
-       /* exits on errors */
-       oss_write_cmdline_parser(argc, argv, conf);
-       return conf;
-}
-
-static void oss_free_config(void *conf)
-{
-       oss_write_cmdline_parser_free(conf);
-}
-
-/**
- * The init function of the oss writer.
- *
- * \param w Pointer to the writer to initialize.
- *
- * \sa struct writer.
- */
-void oss_write_init(struct writer *w)
-{
-       struct oss_write_args_info dummy;
-
-       oss_write_cmdline_parser_init(&dummy);
-       w->close = oss_close;
-       w->pre_select = oss_pre_select;
-       w->post_select = oss_post_select;
-       w->parse_config_or_die = oss_parse_config_or_die;
-       w->free_config = oss_free_config;
-       w->help = (struct ggo_help)DEFINE_GGO_HELP(oss_write);
-       oss_write_cmdline_parser_free(&dummy);
-}
+const struct writer lsg_write_cmd_com_oss_user_data = {
+       .pre_select = oss_pre_select,
+       .post_select = oss_post_select,
+       .close = oss_close,
+};
diff --git a/osx_write.c b/osx_write.c
deleted file mode 100644 (file)
index 18a2c08..0000000
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file osx_write.c paraslash's output plugin for MacOs */
-
-/*
- * based on mosx-mpg123, by Guillaume Outters and Steven A. Kortze
- * <skortze@sourceforge.net>
- */
-
-#include <regex.h>
-#include <sys/types.h>
-
-#include "para.h"
-#include "fd.h"
-#include "string.h"
-#include "list.h"
-#include "sched.h"
-#include "ggo.h"
-#include "buffer_tree.h"
-#include "write.h"
-#include "write_common.h"
-#include "osx_write.cmdline.h"
-#include "ipc.h"
-#include "error.h"
-
-#include <CoreServices/CoreServices.h>
-#include <AudioUnit/AudioUnit.h>
-#include <AudioToolbox/AudioToolbox.h>
-
-/** Data specific to the osx writer. */
-struct private_osx_write_data {
-       /** The main CoreAudio handle. */
-       AudioUnit audio_unit;
-       /** True if we wrote some audio data. */
-       bool playing;
-       /** Sample rate of the current audio stream. */
-       unsigned sample_rate;
-       /** Sample format of the current audio stream */
-       unsigned sample_format;
-       /** Number of channels of the current audio stream. */
-       unsigned channels;
-       /**
-        * Serializes access to buffer tree nodes between the writer and
-        * the callback which runs in a different thread.
-        */
-       int mutex;
-       /**
-        * The btr node of the callback.
-        *
-        * Although access to the btr node is serialized between the writer and
-        * the callback via the above mutex, this does not stop other buffer
-        * tree nodes, for example the decoder, to race against the osx
-        * callback.
-        *
-        * However, since all operations on buffer tree nodes are local in the
-        * sense that they only affect one level in the buffer tree (i.e.
-        * parent or child nodes, but not the grandparent or the
-        * grandchildren), we may work around this problem by using another
-        * buffer tree node for the callback.
-        *
-        * The writer grabs the mutex in its post_select method and pushes down
-        * the buffers to the callback node.
-        */
-       struct btr_node *callback_btrn;
-};
-
-/* This function writes the address and the number of bytes to one end of the socket.
- * The post_select() function then fills the buffer and notifies the callback also
- * through the socket.
- */
-static OSStatus osx_callback(void *cb_arg, __a_unused AudioUnitRenderActionFlags *af,
-               __a_unused const AudioTimeStamp *ts, __a_unused  UInt32 bus_number,
-               __a_unused UInt32 num_frames, AudioBufferList *abl)
-{
-       int i;
-       struct writer_node *wn = cb_arg;
-       struct private_osx_write_data *powd;
-       size_t samples_have, samples_want = 0;
-
-       powd = wn->private_data;
-       mutex_lock(powd->mutex);
-       powd = wn->private_data;
-       if (!powd || !wn->btrn)
-               goto out;
-       /*
-        * We fill with zeros if no data was yet written and we do not have
-        * enough to fill all buffers.
-        */
-       if (!powd->playing) {
-               size_t want = 0, have =
-                       btr_get_input_queue_size(powd->callback_btrn);
-               for (i = 0; i < abl->mNumberBuffers; i++)
-                       want += abl->mBuffers[i].mDataByteSize;
-               if (have < want) {
-                       PARA_DEBUG_LOG("deferring playback (have = %zu < %zu = want)\n",
-                               have, want);
-                       for (i = 0; i < abl->mNumberBuffers; i++)
-                               memset(abl->mBuffers[i].mData, 0,
-                                       abl->mBuffers[i].mDataByteSize);
-                       goto out;
-               }
-               powd->playing = true;
-       }
-
-       for (i = 0; i < abl->mNumberBuffers; i++) {
-               /* what we have to fill */
-               void *dest = abl->mBuffers[i].mData;
-               size_t sz = abl->mBuffers[i].mDataByteSize, samples, bytes;
-
-               samples_want = sz / wn->min_iqs;
-               while (samples_want > 0) {
-                       char *buf;
-                       btr_merge(powd->callback_btrn, wn->min_iqs);
-                       samples_have = btr_next_buffer(powd->callback_btrn, &buf) / wn->min_iqs;
-                       //PARA_INFO_LOG("i: %d want %zu samples to addr %p, have: %zu\n", i, samples_want,
-                       //      dest, samples_have);
-                       samples = PARA_MIN(samples_have, samples_want);
-                       if (samples == 0)
-                               break;
-                       bytes = samples * wn->min_iqs;
-                       memcpy(dest, buf, bytes);
-                       btr_consume(powd->callback_btrn, bytes);
-                       samples_want -= samples;
-                       dest += bytes;
-               }
-               if (samples_want == 0)
-                       continue;
-               if (btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF) >= 0)
-                       PARA_INFO_LOG("zero-padding (%zu samples)\n",
-                               samples_want);
-               memset(dest, 0, samples_want * wn->min_iqs);
-               break;
-       }
-out:
-       mutex_unlock(powd->mutex);
-       return noErr;
-}
-
-static int core_audio_init(struct writer_node *wn)
-{
-       struct private_osx_write_data *powd = para_calloc(sizeof(*powd));
-       Component comp;
-       int ret;
-       int32_t val;
-       AURenderCallbackStruct input_callback;
-       ComponentDescription desc = {
-               .componentType = kAudioUnitType_Output,
-               .componentSubType = kAudioUnitSubType_DefaultOutput,
-               .componentManufacturer = kAudioUnitManufacturer_Apple,
-       };
-       AudioStreamBasicDescription format = {
-               .mFormatID = kAudioFormatLinearPCM,
-               .mFramesPerPacket = 1,
-       };
-       struct btr_node *btrn = wn->btrn;
-       struct btr_node_description bnd;
-
-       PARA_INFO_LOG("wn: %p\n", wn);
-       ret = -E_DEFAULT_COMP;
-       comp = FindNextComponent(NULL, &desc);
-       if (!comp)
-               goto e0;
-       ret = -E_OPEN_COMP;
-       if (OpenAComponent(comp, &powd->audio_unit))
-               goto e0;
-       ret = -E_UNIT_INIT;
-       if (AudioUnitInitialize(powd->audio_unit))
-               goto e1;
-       get_btr_sample_rate(btrn, &val);
-       powd->sample_rate = val;
-       get_btr_channels(btrn, &val);
-       powd->channels = val;
-       get_btr_sample_format(btrn, &val);
-       powd->sample_format = val;
-       /*
-        * Choose PCM format. We tell the Output Unit what format we're going
-        * to supply data to it. This is necessary if you're providing data
-        * through an input callback AND you want the DefaultOutputUnit to do
-        * any format conversions necessary from your format to the device's
-        * format.
-        */
-
-       format.mSampleRate = powd->sample_rate;
-       format.mChannelsPerFrame = powd->channels;
-
-       switch (powd->sample_format) {
-       case SF_S8:
-       case SF_U8:
-               wn->min_iqs = powd->channels;
-               format.mBitsPerChannel = 8;
-               format.mBytesPerPacket = powd->channels;
-               format.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
-               break;
-       default:
-               wn->min_iqs = powd->channels * 2;
-               format.mBytesPerPacket = powd->channels * 2;
-               format.mBitsPerChannel = 16;
-               format.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
-       }
-       format.mBytesPerFrame = format.mBytesPerPacket;
-
-       if (powd->sample_format == SF_S16_BE || powd->sample_format == SF_U16_BE)
-               format.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
-
-       input_callback = (AURenderCallbackStruct){osx_callback, wn};
-       ret = -E_STREAM_FORMAT;
-       if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_StreamFormat,
-                       kAudioUnitScope_Input, 0, &format, sizeof(format)))
-               goto e2;
-       ret = -E_ADD_CALLBACK;
-       if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback,
-                       kAudioUnitScope_Input, 0, &input_callback,
-                       sizeof(input_callback)) < 0)
-               goto e2;
-
-       ret = mutex_new();
-       if (ret < 0)
-               goto e2;
-       powd->mutex = ret;
-       /* set up callback btr node */
-       bnd.name = "cb_node";
-       bnd.parent = btrn;
-       bnd.child = NULL;
-       bnd.handler = NULL;
-       bnd.context = powd;
-       powd->callback_btrn = btr_new_node(&bnd);
-       wn->private_data = powd;
-       return 1;
-e2:
-       AudioUnitUninitialize(powd->audio_unit);
-e1:
-       CloseComponent(powd->audio_unit);
-e0:
-       free(powd);
-       wn->private_data = NULL;
-       return ret;
-}
-
-__malloc static void *osx_write_parse_config_or_die(int argc, char **argv)
-{
-       struct osx_write_args_info *conf = para_calloc(sizeof(*conf));
-
-       /* exits on errors */
-       osx_write_cmdline_parser(argc, argv, conf);
-       return conf;
-}
-
-static void osx_free_config(void *conf)
-{
-       osx_write_cmdline_parser_free(conf);
-}
-
-static void osx_write_close(struct writer_node *wn)
-{
-       struct private_osx_write_data *powd = wn->private_data;
-
-       if (!powd)
-               return;
-       PARA_INFO_LOG("closing writer node %p\n", wn);
-       mutex_destroy(powd->mutex);
-       free(powd);
-       wn->private_data = NULL;
-}
-
-/* must be called with the mutex held */
-static inline bool need_drain_delay(struct private_osx_write_data *powd)
-{
-       if (!powd->playing)
-               return false;
-       return btr_get_input_queue_size(powd->callback_btrn) != 0;
-}
-
-static void osx_write_pre_select(struct sched *s, void *context)
-{
-       struct writer_node *wn = context;
-       struct private_osx_write_data *powd = wn->private_data;
-       int ret;
-       bool drain_delay_nec = false;
-
-       if (!powd) {
-               ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
-               if (ret != 0)
-                       sched_min_delay(s);
-               return;
-       }
-
-       mutex_lock(powd->mutex);
-       ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_INTERNAL);
-       if (ret < 0)
-               drain_delay_nec = need_drain_delay(powd);
-       mutex_unlock(powd->mutex);
-
-       if (drain_delay_nec)
-               return sched_request_timeout_ms(50, s);
-       if (ret != 0)
-               return sched_min_delay(s);
-       sched_request_timeout_ms(50, s);
-}
-
-static int osx_write_post_select(__a_unused struct sched *s, void *context)
-{
-       struct writer_node *wn = context;
-       struct private_osx_write_data *powd = wn->private_data;
-       struct btr_node *btrn = wn->btrn;
-       int ret;
-
-       ret = task_get_notification(wn->task);
-       if (ret < 0)
-               goto fail;
-       if (!powd) {
-               ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
-               if (ret == 0)
-                       return 0;
-               if (ret < 0)
-                       goto fail;
-               ret = core_audio_init(wn);
-               if (ret < 0)
-                       goto fail;
-               powd = wn->private_data;
-               ret = -E_UNIT_START;
-               if (AudioOutputUnitStart(powd->audio_unit) != noErr) {
-                       AudioUnitUninitialize(powd->audio_unit);
-                       CloseComponent(powd->audio_unit);
-                       btr_remove_node(&powd->callback_btrn);
-                       goto fail;
-               }
-       }
-       mutex_lock(powd->mutex);
-       ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_INTERNAL);
-       if (ret > 0)
-               btr_pushdown(btrn);
-       if (ret < 0 && need_drain_delay(powd))
-               ret = 0;
-       mutex_unlock(powd->mutex);
-       if (ret >= 0)
-               return 0;
-fail:
-       assert(ret < 0);
-       if (powd && powd->callback_btrn) {
-               AudioOutputUnitStop(powd->audio_unit);
-               AudioUnitUninitialize(powd->audio_unit);
-               CloseComponent(powd->audio_unit);
-               btr_remove_node(&powd->callback_btrn);
-       }
-       btr_remove_node(&wn->btrn);
-       PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
-       return ret;
-}
-
-/**
- * The init function of the osx writer.
- *
- * \param w Filled in by the function.
- */
-void osx_write_init(struct writer *w)
-{
-       struct osx_write_args_info dummy;
-
-       osx_write_cmdline_parser_init(&dummy);
-       w->close = osx_write_close;
-       w->pre_select = osx_write_pre_select;
-       w->post_select = osx_write_post_select;
-       w->parse_config_or_die = osx_write_parse_config_or_die;
-       w->free_config = osx_free_config;
-       w->help = (struct ggo_help)DEFINE_GGO_HELP(osx_write);
-       osx_write_cmdline_parser_free(&dummy);
-}
diff --git a/para.h b/para.h
index 12d236391dcfb13e18d50450164517346e77398e..b406818bd52a27bfc1794e0c95d8f9c0e9ef12a0 100644 (file)
--- a/para.h
+++ b/para.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file para.h global paraslash definitions */
 
@@ -103,15 +99,6 @@ void compute_chunk_time(long unsigned chunk_num,
                struct timeval *result);
 struct timeval *clock_get_realtime(struct timeval *tv);
 
-/** The enum of all status items. */
-enum status_items {STATUS_ITEM_ENUM NUM_STAT_ITEMS};
-extern const char *status_item_list[];
-/** Loop over each status item. */
-#define FOR_EACH_STATUS_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++)
-int for_each_stat_item(char *item_buf, size_t num_bytes,
-       int (*item_handler)(int, char *));
-
-
 /**
  * Return a random non-negative integer in an interval.
  *
@@ -223,24 +210,9 @@ enum sample_format {SAMPLE_FORMATS};
 #define SAMPLE_FORMAT(a, b) b
 /** \endcond sample_format */
 
-/** Debug loglevel, gets really noisy. */
-#define LL_DEBUG 0
-/** Still noisy, but won't fill your disk. */
-#define LL_INFO  1
-/** Normal, but significant event. */
-#define LL_NOTICE 2
-/** Unexpected event that can be handled. */
-#define LL_WARNING 3
-/** Unhandled error condition. */
-#define LL_ERROR 4
-/** System might be unreliable. */
-#define LL_CRIT 5
-/** Last message before exit. */
-#define LL_EMERG 6
-/** Number of all loglevels. */
-#define NUM_LOGLEVELS 7
-
-/** \cond log */
+/** Debug, Info, etc. */
+enum loglevels {LOGLEVELS, NUM_LOGLEVELS};
+
 #define PARA_DEBUG_LOG(f,...) para_log(LL_DEBUG, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
 #define PARA_INFO_LOG(f,...) para_log(LL_INFO, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
 #define PARA_NOTICE_LOG(f,...) para_log(LL_NOTICE, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
@@ -248,4 +220,57 @@ enum sample_format {SAMPLE_FORMATS};
 #define PARA_ERROR_LOG(f,...) para_log(LL_ERROR, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
 #define PARA_CRIT_LOG(f,...) para_log(LL_CRIT, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
 #define PARA_EMERG_LOG(f,...) para_log(LL_EMERG, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
-/** \endcond log */
+
+#define STATUS_ITEMS \
+       STATUS_ITEM(basename) \
+       STATUS_ITEM(status) \
+       STATUS_ITEM(num_played) \
+       STATUS_ITEM(mtime) \
+       STATUS_ITEM(bitrate) \
+       STATUS_ITEM(frequency) \
+       STATUS_ITEM(file_size) \
+       STATUS_ITEM(status_flags) \
+       STATUS_ITEM(format) \
+       STATUS_ITEM(score) \
+       STATUS_ITEM(techinfo) \
+       STATUS_ITEM(afs_mode) \
+       STATUS_ITEM(attributes_txt) \
+       STATUS_ITEM(decoder_flags) \
+       STATUS_ITEM(audiod_status) \
+       STATUS_ITEM(play_time) \
+       STATUS_ITEM(attributes_bitmap) \
+       STATUS_ITEM(offset) \
+       STATUS_ITEM(seconds_total) \
+       STATUS_ITEM(stream_start) \
+       STATUS_ITEM(current_time) \
+       STATUS_ITEM(audiod_uptime) \
+       STATUS_ITEM(image_id) \
+       STATUS_ITEM(lyrics_id) \
+       STATUS_ITEM(duration) \
+       STATUS_ITEM(directory) \
+       STATUS_ITEM(lyrics_name) \
+       STATUS_ITEM(image_name) \
+       STATUS_ITEM(path) \
+       STATUS_ITEM(hash) \
+       STATUS_ITEM(channels) \
+       STATUS_ITEM(last_played) \
+       STATUS_ITEM(num_chunks) \
+       STATUS_ITEM(chunk_time) \
+       STATUS_ITEM(amplification) \
+       STATUS_ITEM(artist) \
+       STATUS_ITEM(title) \
+       STATUS_ITEM(year) \
+       STATUS_ITEM(album) \
+       STATUS_ITEM(comment) \
+       STATUS_ITEM(max_chunk_size) \
+
+#define STATUS_ITEM(_name) SI_ ##_name,
+enum status_items {STATUS_ITEMS NUM_STAT_ITEMS};
+#undef STATUS_ITEM
+#define STATUS_ITEM(_name) #_name,
+
+extern const char *status_item_list[];
+/** Loop over each status item. */
+#define FOR_EACH_STATUS_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++)
+int for_each_stat_item(char *item_buf, size_t num_bytes,
+       int (*item_handler)(int, char *));
diff --git a/play.c b/play.c
index fac551aa161d14f258e1495a4530462ca1146357..ffdc8555ce6686833b846f9610a3d81f6adf03cb 100644 (file)
--- a/play.c
+++ b/play.c
@@ -1,20 +1,19 @@
-/*
- * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file play.c Paraslash's standalone player. */
 
 #include <regex.h>
-#include <fnmatch.h>
 #include <signal.h>
+#include <lopsub.h>
 
+#include "recv_cmd.lsg.h"
+#include "play_cmd.lsg.h"
+#include "write_cmd.lsg.h"
+#include "play.lsg.h"
 #include "para.h"
+#include "lsu.h"
 #include "list.h"
-#include "play.cmdline.h"
 #include "error.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "version.h"
 #include "string.h"
@@ -23,7 +22,6 @@
 #include "afh.h"
 #include "recv.h"
 #include "write.h"
-#include "write_common.h"
 #include "fd.h"
 
 /**
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
+static struct lls_parse_result *play_lpr;
+
+#define CMD_PTR (lls_cmd(0, play_suite))
+#define OPT_RESULT(_name) \
+       (lls_opt_result(LSG_PLAY_PARA_PLAY_OPT_ ## _name, play_lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+
 /**
  * Describes a request to change the state of para_play.
  *
@@ -75,7 +82,7 @@ struct play_task {
        struct filter_node fn;
        struct writer_node wn;
 
-       /* See comment to enum state_change_request_type above */
+       /* See comment to enum \ref state_change_request_type above. */
        enum state_change_request_type rq;
        /* only relevant if rq == CRT_FILE_CHANGE */
        unsigned next_file;
@@ -98,13 +105,10 @@ struct play_task {
        char *afhi_txt;
 };
 
-/* Activate the afh receiver. */
-extern void afh_recv_init(struct receiver *r);
-#undef AFH_RECEIVER
-/** Initialization code for a receiver struct. */
-#define AFH_RECEIVER {.name = "afh", .init = afh_recv_init},
-/** This expands to the array of all receivers. */
-DEFINE_RECEIVER_ARRAY;
+typedef int (*play_cmd_handler_t)(struct lls_parse_result *lpr);
+struct play_command_info {
+       play_cmd_handler_t handler;
+};
 
 static int loglevel = LL_WARNING;
 
@@ -113,90 +117,71 @@ INIT_STDERR_LOGGING(loglevel);
 
 char *stat_item_values[NUM_STAT_ITEMS] = {NULL};
 
-/** Iterate over all files in the playlist. */
-#define FOR_EACH_PLAYLIST_FILE(i) for (i = 0; i < conf.inputs_num; i++)
-static struct play_args_info conf;
-
 static struct sched sched = {.max_fileno = 0};
-static struct play_task play_task;
-static struct receiver *afh_recv;
+static struct play_task play_task, *pt = &play_task;
 
-static void check_afh_receiver_or_die(void)
-{
-       int i;
+#define AFH_RECV_CMD (lls_cmd(LSG_RECV_CMD_CMD_AFH, recv_cmd_suite))
+#define AFH_RECV ((struct receiver *)lls_user_data(AFH_RECV_CMD))
 
-       FOR_EACH_RECEIVER(i) {
-               struct receiver *r = receivers + i;
-               if (strcmp(r->name, "afh"))
-                       continue;
-               afh_recv = r;
-               return;
-       }
-       PARA_EMERG_LOG("fatal: afh receiver not found\n");
-       exit(EXIT_FAILURE);
+static unsigned *shuffle_map;
+
+static const char *get_playlist_file(unsigned idx)
+{
+       return lls_input(shuffle_map[idx], play_lpr);
 }
 
-__noreturn static void print_help_and_die(void)
+static void handle_help_flags(void)
 {
-       struct ggo_help help = DEFINE_GGO_HELP(play);
-       unsigned flags = conf.detailed_help_given?
-               GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS;
+       char *help;
 
-       ggo_print_help(&help, flags);
-       printf("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
-       exit(0);
+       if (OPT_GIVEN(DETAILED_HELP))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP) || lls_num_inputs(play_lpr) == 0)
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       exit(EXIT_SUCCESS);
 }
 
 static void parse_config_or_die(int argc, char *argv[])
 {
        int i, ret;
-       char *config_file;
-       struct play_cmdline_parser_params params = {
-               .override = 0,
-               .initialize = 1,
-               .check_required = 0,
-               .check_ambiguity = 0,
-               .print_errors = 1
-       };
+       unsigned num_kmas;
+       char *errctx;
 
-       play_cmdline_parser_ext(argc, argv, &conf, &params);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       version_handle_flag("play", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-       if (conf.config_file_given)
-               config_file = para_strdup(conf.config_file_arg);
-       else {
-               char *home = para_homedir();
-               config_file = make_message("%s/.paraslash/play.conf", home);
-               free(home);
-       }
-       ret = file_exists(config_file);
-       if (conf.config_file_given && !ret) {
-               PARA_EMERG_LOG("can not read config file %s\n", config_file);
-               goto err;
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &play_lpr, &errctx));
+       if (ret < 0) {
+               if (errctx)
+                       PARA_EMERG_LOG("%s\n", errctx);
+               free(errctx);
+               PARA_EMERG_LOG("failed to parse command line options: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
-       if (ret) {
-               params.initialize = 0;
-               params.check_required = 1;
-               play_cmdline_parser_config_file(config_file, &conf, &params);
-               loglevel = get_loglevel_by_name(conf.loglevel_arg);
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       version_handle_flag("play", OPT_GIVEN(VERSION));
+       handle_help_flags(); /* also handles the zero-arg case */
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "play.conf", &play_lpr, CMD_PTR, play_suite, 0 /* flags */);
+       if (ret < 0) {
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
-       for (i = 0; i < conf.key_map_given; i++) {
-               char *kma = conf.key_map_arg[i];
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       num_kmas = OPT_GIVEN(KEY_MAP);
+       for (i = 0; i < num_kmas; i++) {
+               const char *kma = lls_string_val(i, OPT_RESULT(KEY_MAP));
                if (*kma && strchr(kma + 1, ':'))
                        continue;
                PARA_EMERG_LOG("invalid key map arg: %s\n", kma);
-               goto err;
+               exit(EXIT_FAILURE);
        }
-       free(config_file);
-       return;
-err:
-       free(config_file);
-       exit(EXIT_FAILURE);
 }
 
-static char get_playback_state(struct play_task *pt)
+static char get_playback_state(void)
 {
        switch (pt->rq) {
        case CRT_NONE: return pt->playing? 'P' : 'U';
@@ -207,9 +192,9 @@ static char get_playback_state(struct play_task *pt)
        assert(false);
 };
 
-static long unsigned get_play_time(struct play_task *pt)
+static long unsigned get_play_time(void)
 {
-       char state = get_playback_state(pt);
+       char state = get_playback_state();
        long unsigned result;
 
        if (state != 'P' && state != 'U')
@@ -230,17 +215,17 @@ static long unsigned get_play_time(struct play_task *pt)
        return result;
 }
 
-static void wipe_receiver_node(struct play_task *pt)
+static void wipe_receiver_node(void)
 {
        PARA_NOTICE_LOG("cleaning up receiver node\n");
        btr_remove_node(&pt->rn.btrn);
-       afh_recv->close(&pt->rn);
-       afh_recv->free_config(pt->rn.conf);
+       AFH_RECV->close(&pt->rn);
+       lls_free_parse_result(pt->rn.lpr, AFH_RECV_CMD);
        memset(&pt->rn, 0, sizeof(struct receiver_node));
 }
 
 /* returns: 0 not eof, 1: eof, < 0: fatal error.  */
-static int get_playback_error(struct play_task *pt)
+static int get_playback_error(void)
 {
        int err;
 
@@ -259,22 +244,23 @@ static int get_playback_error(struct play_task *pt)
        return err;
 }
 
-static int eof_cleanup(struct play_task *pt)
+static int eof_cleanup(void)
 {
-       struct writer *w = writers + DEFAULT_WRITER;
-       const struct filter *decoder = filter_get(pt->fn.filter_num);
+       const struct filter *decoder;
+       const struct writer *w = writer_get(-1); /* default writer */
        int ret;
 
-       ret = get_playback_error(pt);
+       ret = get_playback_error();
        if (ret == 0)
                return ret;
        PARA_NOTICE_LOG("cleaning up wn/fn nodes\n");
        task_reap(&pt->wn.task);
        w->close(&pt->wn);
        btr_remove_node(&pt->wn.btrn);
-       w->free_config(pt->wn.conf);
+       lls_free_parse_result(pt->wn.lpr, WRITE_CMD(pt->wn.wid));
        memset(&pt->wn, 0, sizeof(struct writer_node));
 
+       decoder = filter_get(pt->fn.filter_num);
        task_reap(&pt->fn.task);
        if (decoder->close)
                decoder->close(&pt->fn);
@@ -290,7 +276,7 @@ static int eof_cleanup(struct play_task *pt)
         * paused.
         */
        if (ret < 0)
-               wipe_receiver_node(pt);
+               wipe_receiver_node();
        return ret;
 }
 
@@ -299,34 +285,42 @@ static int shuffle_compare(__a_unused const void *a, __a_unused const void *b)
        return para_random(100) - 50;
 }
 
-static void shuffle(char **base, size_t num)
+static void init_shuffle_map(void)
 {
+       unsigned n, num_inputs = lls_num_inputs(play_lpr);
+       shuffle_map = para_malloc(num_inputs * sizeof(unsigned));
+       for (n = 0; n < num_inputs; n++)
+               shuffle_map[n] = n;
+       if (!OPT_GIVEN(RANDOMIZE))
+               return;
        srandom(time(NULL));
-       qsort(base, num, sizeof(char *), shuffle_compare);
+       qsort(shuffle_map, num_inputs, sizeof(unsigned), shuffle_compare);
 }
 
 static struct btr_node *new_recv_btrn(struct receiver_node *rn)
 {
        return btr_new_node(&(struct btr_node_description)
-               EMBRACE(.name = afh_recv->name, .context = rn,
-                       .handler = afh_recv->execute));
+               EMBRACE(.name = lls_command_name(AFH_RECV_CMD), .context = rn,
+                       .handler = AFH_RECV->execute));
 }
 
-static int open_new_file(struct play_task *pt)
+static int open_new_file(void)
 {
        int ret;
-       char *tmp, *path = conf.inputs[pt->next_file], *afh_recv_conf[] =
-               {"play", "-f", path, "-b", "0", NULL};
+       const char *path = get_playlist_file(pt->next_file);
+       char *tmp = para_strdup(path), *errctx;
+       char *argv[] = {"play", "-f", tmp, "-b", "0", NULL};
 
        PARA_NOTICE_LOG("next file: %s\n", path);
-       wipe_receiver_node(pt);
+       wipe_receiver_node();
        pt->start_chunk = 0;
        pt->rn.btrn = new_recv_btrn(&pt->rn);
-       pt->rn.conf = afh_recv->parse_config(ARRAY_SIZE(afh_recv_conf) - 1,
-               afh_recv_conf);
-       assert(pt->rn.conf);
-       pt->rn.receiver = afh_recv;
-       ret = afh_recv->open(&pt->rn);
+       ret = lls(lls_parse(ARRAY_SIZE(argv) - 1, argv, AFH_RECV_CMD,
+               &pt->rn.lpr, &errctx));
+       free(tmp);
+       assert(ret >= 0);
+       pt->rn.receiver = AFH_RECV;
+       ret = AFH_RECV->open(&pt->rn);
        if (ret < 0) {
                PARA_ERROR_LOG("could not open %s\n", path);
                goto fail;
@@ -358,20 +352,21 @@ static int open_new_file(struct play_task *pt)
        }
        return 1;
 fail:
-       wipe_receiver_node(pt);
+       wipe_receiver_node();
        return ret;
 }
 
-static int load_file(struct play_task *pt)
+static int load_file(void)
 {
        const char *af;
        char *tmp, buf[20];
        int ret;
        const struct filter *decoder;
+       static struct lls_parse_result *filter_lpr, *writer_lpr;
 
        btr_remove_node(&pt->rn.btrn);
        if (!pt->rn.receiver || pt->next_file != pt->current_file) {
-               ret = open_new_file(pt);
+               ret = open_new_file();
                if (ret < 0)
                        return ret;
        } else {
@@ -387,30 +382,31 @@ static int load_file(struct play_task *pt)
        /* set up decoding filter */
        af = audio_format_name(pt->audio_format_num);
        tmp = make_message("%sdec", af);
-       PARA_INFO_LOG("decoder: %s\n", tmp);
-       ret = check_filter_arg(tmp, &pt->fn.conf);
+       ret = filter_setup(tmp, &pt->fn.conf, &filter_lpr);
        freep(&tmp);
        if (ret < 0)
                goto fail;
        pt->fn.filter_num = ret;
+       pt->fn.lpr = filter_lpr;
        decoder = filter_get(ret);
        pt->fn.btrn = btr_new_node(&(struct btr_node_description)
-               EMBRACE(.name = decoder->name, .parent = pt->rn.btrn,
-                       .handler = decoder->execute, .context = &pt->fn));
+               EMBRACE(.name = filter_name(pt->fn.filter_num),
+                       .parent = pt->rn.btrn, .handler = decoder->execute,
+                       .context = &pt->fn));
        if (decoder->open)
                decoder->open(&pt->fn);
        PARA_INFO_LOG("buffer tree:\n");
        btr_log_tree(pt->rn.btrn, LL_INFO);
 
        /* setup default writer */
-       pt->wn.conf = check_writer_arg_or_die(NULL, &pt->wn.writer_num);
-
+       pt->wn.wid = check_writer_arg_or_die(NULL, &writer_lpr);
+       pt->wn.lpr = writer_lpr;
        /* success, register tasks */
        pt->rn.task = task_register(
                &(struct task_info) {
-                       .name = afh_recv->name,
-                       .pre_select = afh_recv->pre_select,
-                       .post_select = afh_recv->post_select,
+                       .name = lls_command_name(AFH_RECV_CMD),
+                       .pre_select = AFH_RECV->pre_select,
+                       .post_select = AFH_RECV->post_select,
                        .context = &pt->rn
                }, &sched);
        sprintf(buf, "%s decoder", af);
@@ -424,36 +420,46 @@ static int load_file(struct play_task *pt)
        register_writer_node(&pt->wn, pt->fn.btrn, &sched);
        return 1;
 fail:
-       wipe_receiver_node(pt);
+       wipe_receiver_node();
        return ret;
 }
 
-static int next_valid_file(struct play_task *pt)
+static int next_valid_file(void)
 {
        int i, j = pt->current_file;
+       unsigned num_inputs = lls_num_inputs(play_lpr);
 
-       FOR_EACH_PLAYLIST_FILE(i) {
-               j = (j + 1) % conf.inputs_num;
+       if (j == num_inputs - 1) {
+               switch (OPT_UINT32_VAL(END_OF_PLAYLIST)) {
+               case EOP_LOOP: break;
+               case EOP_STOP:
+                       pt->playing = false;
+                       return 0;
+               case EOP_QUIT: return -E_EOP;
+               }
+       }
+       for (i = 0; i < num_inputs; i++) {
+               j = (j + 1) % num_inputs;
                if (!pt->invalid[j])
                        return j;
        }
        return -E_NO_VALID_FILES;
 }
 
-static int load_next_file(struct play_task *pt)
+static int load_next_file(void)
 {
        int ret;
 
 again:
        if (pt->rq == CRT_NONE) {
                pt->start_chunk = 0;
-               ret = next_valid_file(pt);
+               ret = next_valid_file();
                if (ret < 0)
                        return ret;
                pt->next_file = ret;
        } else if (pt->rq == CRT_REPOS)
                pt->next_file = pt->current_file;
-       ret = load_file(pt);
+       ret = load_file();
        if (ret < 0) {
                PARA_ERROR_LOG("%s: marking file as invalid\n",
                        para_strerror(-ret));
@@ -466,7 +472,7 @@ again:
        return ret;
 }
 
-static void kill_stream(struct play_task *pt)
+static void kill_stream(void)
 {
        if (pt->wn.task)
                task_notify(pt->wn.task, E_EOF);
@@ -475,14 +481,15 @@ static void kill_stream(struct play_task *pt)
 #ifdef HAVE_READLINE
 
 /* only called from com_prev(), nec. only if we have readline */
-static int previous_valid_file(struct play_task *pt)
+static int previous_valid_file(void)
 {
        int i, j = pt->current_file;
+       unsigned num_inputs = lls_num_inputs(play_lpr);
 
-       FOR_EACH_PLAYLIST_FILE(i) {
+       for (i = 0; i < num_inputs; i++) {
                j--;
                if (j < 0)
-                       j = conf.inputs_num - 1;
+                       j = num_inputs - 1;
                if (!pt->invalid[j])
                        return j;
        }
@@ -533,7 +540,7 @@ static const char *default_keyseqs[] = {INTERNAL_KEYMAP_ENTRIES};
 static const char *default_commands[] = {INTERNAL_KEYMAP_ENTRIES};
 #undef KEYMAP_ENTRY
 #define NUM_INTERNALLY_MAPPED_KEYS ARRAY_SIZE(default_commands)
-#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + conf.key_map_given)
+#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + OPT_GIVEN(KEY_MAP))
 #define FOR_EACH_MAPPED_KEY(i) for (i = 0; i < NUM_MAPPED_KEYS; i++)
 
 static inline bool is_internal_key(int key)
@@ -564,9 +571,9 @@ static inline int get_key_map_idx(int key)
                get_internal_key_map_idx(key) : get_user_key_map_idx(key);
 }
 
-static inline char *get_user_key_map_arg(int key)
+static inline const char *get_user_key_map_arg(int key)
 {
-       return conf.key_map_arg[get_user_key_map_idx(key)];
+       return lls_string_val(get_user_key_map_idx(key), OPT_RESULT(KEY_MAP));
 }
 
 static inline char *get_internal_key_map_seq(int key)
@@ -653,24 +660,6 @@ static char **get_mapped_keyseqs(void)
        return result;
 }
 
-#include "play.command_list.h"
-
-typedef int play_command_handler_t(struct play_task *, int, char**);
-static play_command_handler_t PLAY_COMMAND_HANDLERS;
-
-/* defines one command of para_play */
-struct pp_command {
-       const char *name;
-       play_command_handler_t *handler;
-       const char *description;
-       const char *usage;
-       const char *help;
-};
-
-static struct pp_command pp_cmds[] = {DEFINE_PLAY_CMD_ARRAY};
-#define FOR_EACH_COMMAND(c) for (c = 0; pp_cmds[c].name; c++)
-
-#include "play.completion.h"
 static struct i9e_completer pp_completers[];
 
 I9E_DUMMY_COMPLETER(jmp);
@@ -682,20 +671,31 @@ I9E_DUMMY_COMPLETER(ls);
 I9E_DUMMY_COMPLETER(info);
 I9E_DUMMY_COMPLETER(play);
 I9E_DUMMY_COMPLETER(pause);
-I9E_DUMMY_COMPLETER(stop);
 I9E_DUMMY_COMPLETER(tasks);
 I9E_DUMMY_COMPLETER(quit);
 I9E_DUMMY_COMPLETER(ff);
 
 static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
 {
-       result->matches = i9e_complete_commands(ci->word, pp_completers);
+       char *opts[] = {LSG_PLAY_CMD_HELP_OPTS, NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, pp_completers);
 }
 
-static struct i9e_completer pp_completers[] = {PLAY_COMPLETERS {.name = NULL}};
+static struct i9e_completer pp_completers[] = {
+#define LSG_PLAY_CMD_CMD(_name) {.name = #_name, \
+       .completer = _name ## _completer}
+       LSG_PLAY_CMD_SUBCOMMANDS
+#undef LSG_PLAY_CMD_CMD
+       {.name = NULL}
+};
 
-static void attach_stdout(struct play_task *pt, const char *name)
+static void attach_stdout(const char *name)
 {
        if (pt->btrn)
                return;
@@ -704,141 +704,120 @@ static void attach_stdout(struct play_task *pt, const char *name)
        i9e_attach_to_stdout(pt->btrn);
 }
 
-static void detach_stdout(struct play_task *pt)
+static void detach_stdout(void)
 {
        btr_remove_node(&pt->btrn);
 }
 
-static int com_quit(struct play_task *pt, int argc, __a_unused char **argv)
+#define EXPORT_PLAY_CMD_HANDLER(_cmd) \
+       const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \
+               .handler = com_ ## _cmd \
+       };
+
+static int com_quit(__a_unused struct lls_parse_result *lpr)
 {
-       if (argc != 1)
-               return -E_PLAY_SYNTAX;
        pt->rq = CRT_TERM_RQ;
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(quit);
 
-static int com_help(struct play_task *pt, int argc, char **argv)
+static int com_help(struct lls_parse_result *lpr)
 {
        int i;
        char *buf;
        size_t sz;
-
-       if (argc > 2)
-               return -E_PLAY_SYNTAX;
-       if (argc < 2) {
-               if (pt->background)
-                       FOR_EACH_COMMAND(i) {
-                               sz = xasprintf(&buf, "%s\t%s\n", pp_cmds[i].name,
-                                       pp_cmds[i].description);
-                               btr_add_output(buf, sz, pt->btrn);
-                       }
-               else {
-                       FOR_EACH_MAPPED_KEY(i) {
-                               bool internal = is_internal_key(i);
-                               int idx = get_key_map_idx(i);
-                               char *seq = get_key_map_seq_safe(i);
-                               char *cmd = get_key_map_cmd(i);
-                               sz = xasprintf(&buf,
-                                       "%s key #%d: %s -> %s\n",
-                                       internal? "internal" : "user-defined",
-                                       idx, seq, cmd);
-                               btr_add_output(buf, sz, pt->btrn);
-                               free(seq);
-                               free(cmd);
-                       }
+       unsigned n;
+       const struct lls_opt_result *r =
+               lls_opt_result(LSG_PLAY_CMD_HELP_OPT_LONG, lpr);
+       bool long_help = lls_opt_given(r);
+
+       if (!pt->background) {
+               FOR_EACH_MAPPED_KEY(i) {
+                       bool internal = is_internal_key(i);
+                       int idx = get_key_map_idx(i);
+                       char *seq = get_key_map_seq_safe(i);
+                       char *kmc = get_key_map_cmd(i);
+                       sz = xasprintf(&buf, "%s key #%d: %s -> %s\n",
+                               internal? "internal" : "user-defined",
+                               idx, seq, kmc);
+                       btr_add_output(buf, sz, pt->btrn);
+                       free(seq);
+                       free(kmc);
                }
                return 0;
        }
-       FOR_EACH_COMMAND(i) {
-               if (strcmp(pp_cmds[i].name, argv[1]))
-                       continue;
-               sz = xasprintf(&buf,
-                       "NAME\n\t%s -- %s\n"
-                       "SYNOPSIS\n\t%s\n"
-                       "DESCRIPTION\n%s\n",
-                       argv[1],
-                       pp_cmds[i].description,
-                       pp_cmds[i].usage,
-                       pp_cmds[i].help
-               );
-               btr_add_output(buf, sz, pt->btrn);
-               return 0;
-       }
-       return -E_BAD_PLAY_CMD;
+       lsu_com_help(long_help, lpr, play_cmd_suite, NULL, &buf, &n);
+       btr_add_output(buf, n, pt->btrn);
+       return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(help);
 
-static int com_info(struct play_task *pt, int argc, __a_unused char **argv)
+static int com_info(__a_unused struct lls_parse_result *lpr)
 {
        char *buf;
        size_t sz;
        static char dflt[] = "[no information available]";
 
-       if (argc != 1)
-               return -E_PLAY_SYNTAX;
        sz = xasprintf(&buf, "playlist_pos: %u\npath: %s\n",
-               pt->current_file, conf.inputs[pt->current_file]);
+               pt->current_file, get_playlist_file(pt->current_file));
        btr_add_output(buf, sz, pt->btrn);
        buf = pt->afhi_txt? pt->afhi_txt : dflt;
        btr_add_output_dont_free(buf, strlen(buf), pt->btrn);
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(info);
 
-static void list_file(struct play_task *pt, int num)
+static void list_file(int num)
 {
        char *buf;
        size_t sz;
 
        sz = xasprintf(&buf, "%s %4d %s\n", num == pt->current_file?
-               "*" : " ", num, conf.inputs[num]);
+               "*" : " ", num, get_playlist_file(num));
        btr_add_output(buf, sz, pt->btrn);
 }
 
-static int com_tasks(struct play_task *pt, int argc, __a_unused char **argv)
+static int com_tasks(__a_unused struct lls_parse_result *lpr)
 {
        static char state;
        char *buf;
        size_t sz;
 
-       if (argc != 1)
-               return -E_PLAY_SYNTAX;
-
        buf = get_task_list(&sched);
        btr_add_output(buf, strlen(buf), pt->btrn);
-       state = get_playback_state(pt);
+       state = get_playback_state();
        sz = xasprintf(&buf, "state: %c\n", state);
        btr_add_output(buf, sz, pt->btrn);
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(tasks);
 
-static int com_ls(struct play_task *pt, int argc, char **argv)
+static int com_ls(__a_unused struct lls_parse_result *lpr)
 {
-       int i, j, ret;
+       int i;
+       unsigned num_inputs = lls_num_inputs(play_lpr);
 
-       if (argc == 1) {
-               FOR_EACH_PLAYLIST_FILE(i)
-                       list_file(pt, i);
-               return 0;
-       }
-       for (j = 1; j < argc; j++) {
-               FOR_EACH_PLAYLIST_FILE(i) {
-                       ret = fnmatch(argv[j], conf.inputs[i], 0);
-                       if (ret == 0) /* match */
-                               list_file(pt, i);
-               }
-       }
+       for (i = 0; i < num_inputs; i++)
+               list_file(i);
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(ls);
 
-static int com_play(struct play_task *pt, int argc, char **argv)
+static int com_play(struct lls_parse_result *lpr)
 {
        int32_t x;
        int ret;
-       char state;
+       char state, *errctx;
 
-       if (argc > 2)
-               return -E_PLAY_SYNTAX;
-       state = get_playback_state(pt);
-       if (argc == 1) {
+       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               return ret;
+       }
+       state = get_playback_state();
+       if (lls_num_inputs(lpr) == 0) {
                if (state == 'P')
                        return 0;
                pt->next_file = pt->current_file;
@@ -846,29 +825,28 @@ static int com_play(struct play_task *pt, int argc, char **argv)
                pt->playing = true;
                return 0;
        }
-       ret = para_atoi32(argv[1], &x);
+       ret = para_atoi32(lls_input(0, lpr), &x);
        if (ret < 0)
                return ret;
-       if (x < 0 || x >= conf.inputs_num)
+       if (x < 0 || x >= lls_num_inputs(play_lpr))
                return -ERRNO_TO_PARA_ERROR(EINVAL);
-       kill_stream(pt);
+       kill_stream();
        pt->next_file = x;
        pt->rq = CRT_FILE_CHANGE;
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(play);
 
-static int com_pause(struct play_task *pt, int argc, __a_unused char **argv)
+static int com_pause(__a_unused struct lls_parse_result *lpr)
 {
        char state;
        long unsigned seconds, ss;
 
-       if (argc != 1)
-               return -E_PLAY_SYNTAX;
-       state = get_playback_state(pt);
+       state = get_playback_state();
        pt->playing = false;
        if (state != 'P')
                return 0;
-       seconds = get_play_time(pt);
+       seconds = get_play_time();
        pt->playing = false;
        ss = 0;
        if (pt->seconds > 0)
@@ -876,96 +854,105 @@ static int com_pause(struct play_task *pt, int argc, __a_unused char **argv)
        ss = PARA_MAX(ss, 0UL);
        ss = PARA_MIN(ss, pt->num_chunks);
        pt->start_chunk = ss;
-       kill_stream(pt);
+       kill_stream();
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(pause);
 
-static int com_prev(struct play_task *pt, int argc, __a_unused char **argv)
-
+static int com_prev(__a_unused struct lls_parse_result *lpr)
 {
        int ret;
 
-       if (argc != 1)
-               return -E_PLAY_SYNTAX;
-       ret = previous_valid_file(pt);
+       ret = previous_valid_file();
        if (ret < 0)
                return ret;
-       kill_stream(pt);
+       kill_stream();
        pt->next_file = ret;
        pt->rq = CRT_FILE_CHANGE;
        pt->start_chunk = 0;
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(prev);
 
-static int com_next(struct play_task *pt, int argc, __a_unused char **argv)
+static int com_next(__a_unused struct lls_parse_result *lpr)
 {
        int ret;
 
-       if (argc != 1)
-               return -E_PLAY_SYNTAX;
-       ret = next_valid_file(pt);
+       ret = next_valid_file();
        if (ret < 0)
                return ret;
-       kill_stream(pt);
+       kill_stream();
        pt->next_file = ret;
        pt->rq = CRT_FILE_CHANGE;
        pt->start_chunk = 0;
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(next);
 
-static int com_fg(struct play_task *pt, int argc, __a_unused char **argv)
+static int com_fg(__a_unused struct lls_parse_result *lpr)
 {
-       if (argc != 1)
-               return -E_PLAY_SYNTAX;
        pt->background = false;
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(fg);
 
-static int com_bg(struct play_task *pt, int argc, __a_unused char **argv)
+static int com_bg(__a_unused struct lls_parse_result *lpr)
 {
-       if (argc != 1)
-               return -E_PLAY_SYNTAX;
        pt->background = true;
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(bg);
 
-static int com_jmp(struct play_task *pt, int argc, char **argv)
+static int com_jmp(struct lls_parse_result *lpr)
 {
        int32_t percent;
        int ret;
+       char *errctx;
 
-       if (argc != 2)
-               return -E_PLAY_SYNTAX;
-       ret = para_atoi32(argv[1], &percent);
+       ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               return ret;
+       }
+       ret = para_atoi32(lls_input(0, lpr), &percent);
        if (ret < 0)
                return ret;
        if (percent < 0 || percent > 100)
                return -ERRNO_TO_PARA_ERROR(EINVAL);
        if (percent == 100)
-               return com_next(pt, 1, (char *[]){"next", NULL});
+               return com_next(NULL);
        if (pt->playing && !pt->fn.btrn)
                return 0;
        pt->start_chunk = percent * pt->num_chunks / 100;
        if (!pt->playing)
                return 0;
        pt->rq = CRT_REPOS;
-       kill_stream(pt);
+       kill_stream();
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(jmp);
 
-static int com_ff(struct play_task *pt, int argc, char **argv)
+static int com_ff(struct lls_parse_result *lpr)
 {
        int32_t seconds;
+       char *errctx;
        int ret;
 
-       if (argc != 2)
-               return -E_PLAY_SYNTAX;
-       ret = para_atoi32(argv[1], &seconds);
+       ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               return ret;
+       }
+       ret = para_atoi32(lls_input(0, lpr), &seconds);
        if (ret < 0)
                return ret;
        if (pt->playing && !pt->fn.btrn)
                return 0;
-       seconds += get_play_time(pt);
+       seconds += get_play_time();
        seconds = PARA_MIN(seconds, (typeof(seconds))pt->seconds - 4);
        seconds = PARA_MAX(seconds, 0);
        pt->start_chunk = pt->num_chunks * seconds / pt->seconds;
@@ -974,49 +961,52 @@ static int com_ff(struct play_task *pt, int argc, char **argv)
        if (!pt->playing)
                return 0;
        pt->rq = CRT_REPOS;
-       kill_stream(pt);
+       kill_stream();
        return 0;
 }
+EXPORT_PLAY_CMD_HANDLER(ff);
 
-static int run_command(char *line, struct play_task *pt)
+static int run_command(char *line)
 {
-       int i, ret, argc;
+       int ret, argc;
        char **argv = NULL;
+       char *errctx = NULL;
+       const struct play_command_info *pci;
+       struct lls_parse_result *lpr;
+       const struct lls_command *cmd;
 
-       attach_stdout(pt, __FUNCTION__);
+       attach_stdout(__FUNCTION__);
        ret = create_argv(line, " ", &argv);
-       if (ret < 0) {
-               PARA_ERROR_LOG("parse error: %s\n", para_strerror(-ret));
-               return 0;
-       }
+       if (ret < 0)
+               goto out;
        if (ret == 0)
                goto out;
        argc = ret;
-       FOR_EACH_COMMAND(i) {
-               if (strcmp(pp_cmds[i].name, argv[0]))
-                       continue;
-               ret = pp_cmds[i].handler(pt, argc, argv);
-               if (ret < 0)
-                       PARA_WARNING_LOG("%s: %s\n", pt->background?
-                               "" : argv[0], para_strerror(-ret));
-               ret = 1;
+       ret = lls(lls_lookup_subcmd(argv[0], play_cmd_suite, &errctx));
+       if (ret < 0)
                goto out;
-       }
-       PARA_WARNING_LOG("invalid command: %s\n", argv[0]);
-       ret = 0;
+       cmd = lls_cmd(ret, play_cmd_suite);
+       ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       pci = lls_user_data(cmd);
+       ret = pci->handler(lpr);
+       lls_free_parse_result(lpr, cmd);
 out:
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", errctx);
+       free(errctx);
        free_argv(argv);
        return ret;
 }
 
 static int play_i9e_line_handler(char *line)
 {
-       return run_command(line, &play_task);
+       return run_command(line);
 }
 
 static int play_i9e_key_handler(int key)
 {
-       struct play_task *pt = &play_task;
        int idx = get_key_map_idx(key);
        char *seq = get_key_map_seq(key);
        char *cmd = get_key_map_cmd(key);
@@ -1025,7 +1015,7 @@ static int play_i9e_key_handler(int key)
        PARA_NOTICE_LOG("pressed %d: %s key #%d (%s -> %s)\n",
                key, internal? "internal" : "user-defined",
                idx, seq, cmd);
-       run_command(cmd, pt);
+       run_command(cmd);
        free(seq);
        free(cmd);
        pt->next_update = *now;
@@ -1042,7 +1032,7 @@ static struct i9e_client_info ici = {
 
 static void sigint_handler(int sig)
 {
-       play_task.background = true;
+       pt->background = true;
        i9e_signal_dispatch(sig);
 }
 
@@ -1051,20 +1041,27 @@ static void sigint_handler(int sig)
  * stderr. Once the i9e subsystem has been initialized, we switch to the i9e
  * log facility.
  */
-static void session_open(struct play_task *pt)
+static void session_open(void)
 {
        int ret;
        char *history_file;
        struct sigaction act;
 
        PARA_NOTICE_LOG("\n%s\n", version_text("play"));
-       if (conf.history_file_given)
-               history_file = para_strdup(conf.history_file_arg);
+       if (OPT_GIVEN(HISTORY_FILE))
+               history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE));
        else {
                char *home = para_homedir();
-               history_file = make_message("%s/.paraslash/play.history",
-                       home);
+               char *dot_para = make_message("%s/.paraslash", home);
+
                free(home);
+               ret = para_mkdir(dot_para, 0777);
+               /* warn, but otherwise ignore mkdir error */
+               if (ret < 0 && ret != -ERRNO_TO_PARA_ERROR(EEXIST))
+                       PARA_WARNING_LOG("Can not create %s: %s\n", dot_para,
+                               para_strerror(-ret));
+               history_file = make_message("%s/play.history", dot_para);
+               free(dot_para);
        }
        ici.history_file = history_file;
        ici.loglevel = loglevel;
@@ -1095,7 +1092,7 @@ out:
        exit(EXIT_FAILURE);
 }
 
-static void session_update_time_string(struct play_task *pt, char *str, unsigned len)
+static void session_update_time_string(char *str, unsigned len)
 {
        if (pt->background)
                return;
@@ -1120,30 +1117,30 @@ static void session_update_time_string(struct play_task *pt, char *str, unsigned
  * terminates. Subsequent calls to i9e_get_error() then return negative and we
  * are allowed to call i9e_close() and terminate as well.
  */
-static int session_post_select(__a_unused struct sched *s, struct play_task *pt)
+static int session_post_select(__a_unused struct sched *s)
 {
        int ret;
 
        if (pt->background)
-               detach_stdout(pt);
+               detach_stdout();
        else
-               attach_stdout(pt, __FUNCTION__);
+               attach_stdout(__FUNCTION__);
        ret = i9e_get_error();
        if (ret < 0) {
-               kill_stream(pt);
+               kill_stream();
                i9e_close();
                para_log = stderr_log;
                free(ici.history_file);
                return ret;
        }
-       if (get_playback_state(pt) == 'X')
+       if (get_playback_state() == 'X')
                i9e_signal_dispatch(SIGTERM);
        return 0;
 }
 
 #else /* HAVE_READLINE */
 
-static int session_post_select(struct sched *s, struct play_task *pt)
+static int session_post_select(struct sched *s)
 {
        char c;
 
@@ -1151,38 +1148,36 @@ static int session_post_select(struct sched *s, struct play_task *pt)
                return 0;
        if (read(STDIN_FILENO, &c, 1))
                do_nothing;
-       kill_stream(pt);
+       kill_stream();
        return 1;
 }
 
-static void session_open(__a_unused struct play_task *pt)
+static void session_open(void)
 {
 }
 
-static void session_update_time_string(__a_unused struct play_task *pt,
-               char *str, __a_unused unsigned len)
+static void session_update_time_string(char *str, __a_unused unsigned len)
 {
        printf("\r%s     ", str);
        fflush(stdout);
 }
 #endif /* HAVE_READLINE */
 
-static void play_pre_select(struct sched *s, void *context)
+static void play_pre_select(struct sched *s, __a_unused void *context)
 {
-       struct play_task *pt = context;
        char state;
 
        para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno);
-       state = get_playback_state(pt);
+       state = get_playback_state();
        if (state == 'R' || state == 'F' || state == 'X')
                return sched_min_delay(s);
        sched_request_barrier_or_min_delay(&pt->next_update, s);
 }
 
-static unsigned get_time_string(struct play_task *pt, char **result)
+static unsigned get_time_string(char **result)
 {
        int seconds, length;
-       char state = get_playback_state(pt);
+       char state = get_playback_state();
 
        /* do not return anything if things are about to change */
        if (state != 'P' && state != 'U') {
@@ -1192,7 +1187,7 @@ static unsigned get_time_string(struct play_task *pt, char **result)
        length = pt->seconds;
        if (length == 0)
                return xasprintf(result, "0:00 [0:00] (0%%/0:00)");
-       seconds = get_play_time(pt);
+       seconds = get_play_time();
        return xasprintf(result, "#%u: %d:%02d [%d:%02d] (%d%%/%d:%02d) %s",
                pt->current_file,
                seconds / 60,
@@ -1202,28 +1197,27 @@ static unsigned get_time_string(struct play_task *pt, char **result)
                length? (seconds * 100 + length / 2) / length : 0,
                length / 60,
                length % 60,
-               conf.inputs[pt->current_file]
+               get_playlist_file(pt->current_file)
        );
 }
 
-static int play_post_select(struct sched *s, void *context)
+static int play_post_select(struct sched *s, __a_unused void *context)
 {
-       struct play_task *pt = context;
        int ret;
 
-       ret = eof_cleanup(pt);
+       ret = eof_cleanup();
        if (ret < 0) {
                pt->rq = CRT_TERM_RQ;
                return 0;
        }
-       ret = session_post_select(s, pt);
+       ret = session_post_select(s);
        if (ret < 0)
                goto out;
        if (!pt->wn.btrn && !pt->fn.btrn) {
-               char state = get_playback_state(pt);
+               char state = get_playback_state();
                if (state == 'P' || state == 'R' || state == 'F') {
                        PARA_NOTICE_LOG("state: %c\n", state);
-                       ret = load_next_file(pt);
+                       ret = load_next_file();
                        if (ret < 0) {
                                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
                                pt->rq = CRT_TERM_RQ;
@@ -1235,10 +1229,10 @@ static int play_post_select(struct sched *s, void *context)
        }
        if (tv_diff(now, &pt->next_update, NULL) >= 0) {
                char *str;
-               unsigned len = get_time_string(pt, &str);
+               unsigned len = get_time_string(&str);
                struct timeval delay = {.tv_sec = 0, .tv_usec = 100 * 1000};
                if (str && len > 0)
-                       session_update_time_string(pt, str, len);
+                       session_update_time_string(str, len);
                free(str);
                tv_add(now, &delay, &pt->next_update);
        }
@@ -1258,26 +1252,15 @@ out:
 int main(int argc, char *argv[])
 {
        int ret;
-       struct play_task *pt = &play_task;
-
-       /* needed this early to make help work */
-       recv_init();
-       filter_init();
-       writer_init();
+       unsigned num_inputs;
 
        sched.default_timeout.tv_sec = 5;
-
        parse_config_or_die(argc, argv);
-       if (conf.inputs_num == 0)
-               print_help_and_die();
-       check_afh_receiver_or_die();
-
-       session_open(pt);
-       if (conf.randomize_given)
-               shuffle(conf.inputs, conf.inputs_num);
-       pt->invalid = para_calloc(sizeof(*pt->invalid) * conf.inputs_num);
+       session_open();
+       num_inputs = lls_num_inputs(play_lpr);
+       init_shuffle_map();
+       pt->invalid = para_calloc(sizeof(*pt->invalid) * num_inputs);
        pt->rq = CRT_FILE_CHANGE;
-       pt->current_file = conf.inputs_num - 1;
        pt->playing = true;
        pt->task = task_register(&(struct task_info){
                .name = "play",
diff --git a/play.cmd b/play.cmd
deleted file mode 100644 (file)
index 459ad8c..0000000
--- a/play.cmd
+++ /dev/null
@@ -1,77 +0,0 @@
-BN: play
-SF: play.c
-SN: list of commands
----
-N: help
-D: Display command list or help for given command.
-U: help [command]
-H: This command acts differently depending on whether it is executed in command
-H: mode or in insert mode.  In command mode, the list of keybindings is printed.
-H: In insert mode, if no command is given, the list of commands is shown.
-H: Otherwise, the help for the given command is printed.
----
-N: next
-D: Load next file.
-U: next
-H: Closes the current file and loads the next file of the playlist.
----
-N: prev
-D: Load previous file.
-U: prev
-H: Closes the current file and loads the previous file of the playlist.
----
-N: fg
-D: Enter command mode.
-U: fg
-H: In this mode, file name and play time are displayed.  Hit CTRL+C to switch to
-H: input mode.
----
-N: bg
-D: Enter input mode.
-U: bg
-H: Only useful if called in command mode via a key binding. The default key
-H: bindings map this command to the colon key, so pressing : in command mode
-H: activates insert mode.
----
-N: jmp
-D: Jump to position in current file.
-U: jmp <percent>
-H: The <percent> argument should be an integer between 0 and 100.
----
-N: ff
-D: Jump forwards or backwards.
-U: ff <seconds>
-H: Negative values mean to jmp backwards the given amount of seconds.
----
-N: ls
-D: List playlist.
-U: ls
-H: This prints all paths of the playlist. The currently active file is
-H: marked with an asterisk.
----
-N: info
-D: Print information about the current file.
-U: info
-H: This is the audio file selector info.
----
-N: play
-D: Start or resume playing.
-U: play [<num>]
-H: Without <num>, starts playing at the current position. Otherwise, the
-H: corresponding file is loaded and playback is started.
----
-N: pause
-D: Stop playing.
-U: pause
-H: When paused, it is still possible to jump around in the file via the jmp and ff
-H: comands.
----
-N: tasks
-D: Print list of active tasks.
-U: tasks
-H: Mainly useful for debugging.
----
-N: quit
-D: Exit para_play.
-U: quit
-H: Pressing CTRL+D causes EOF on stdin which also exits para_play.
index 8ea1854bddfc004db52d6d0afe577d6601960b07..5f83b0fe0a35c9e5c31c97828bfa7af2bebc6208 100644 (file)
@@ -1,11 +1,8 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 #include <regex.h>
 #include <osl.h>
+#include <lopsub.h>
 
 #include "para.h"
 #include "error.h"
@@ -139,7 +136,7 @@ int playlist_check_callback(struct afs_callback_arg *aca)
 /**
  * Close the current playlist.
  *
- * \sa playlist_open().
+ * \sa \ref playlist_open().
  */
 void playlist_close(void)
 {
index 4e10c2e3386d1511ac6f71bbfc47210d02852e3a..fdd4165d70e5acda33fe2b97f1b3141730ce0c08 100644 (file)
@@ -1,66 +1,64 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file portable_io.h Inline functions for binary IO. */
 
-static inline uint64_t read_portable(unsigned bits, const char *buf)
+static inline uint64_t read_portable(unsigned bits, const void *buf)
 {
        uint64_t ret = 0;
        int i, num_bytes = bits / 8;
+       const uint8_t *p = (typeof(p))buf;
 
        for (i = 0; i < num_bytes; i++) {
-               unsigned char c = buf[i];
+               unsigned char c = p[i];
                ret += ((uint64_t)c << (8 * i));
        }
        return ret;
 }
 
-static inline uint64_t read_portable_be(unsigned bits, const char *buf)
+static inline uint64_t read_portable_be(unsigned bits, const void *buf)
 {
        uint64_t ret = 0;
        int i, num_bytes = bits / 8;
+       const uint8_t *p = (typeof(p))buf;
 
        for (i = 0; i < num_bytes; i++) {
-               unsigned char c = buf[i];
+               unsigned char c = p[i];
                ret += ((uint64_t)c << (8 * (num_bytes - i - 1)));
        }
        return ret;
 }
 
-static inline uint64_t read_u64(const char *buf)
+static inline uint64_t read_u64(const void *buf)
 {
        return read_portable(64, buf);
 }
 
-static inline uint32_t read_u32(const char *buf)
+static inline uint32_t read_u32(const void *buf)
 {
        return read_portable(32, buf);
 }
 
-static inline uint16_t read_u16(const char *buf)
+static inline uint16_t read_u16(const void *buf)
 {
        return read_portable(16, buf);
 }
 
-static inline uint8_t read_u8(const char *buf)
+static inline uint8_t read_u8(const void *buf)
 {
        return read_portable(8, buf);
 }
 
-static inline uint64_t read_u64_be(const char *buf)
+static inline uint64_t read_u64_be(const void *buf)
 {
        return read_portable_be(64, buf);
 }
 
-static inline uint32_t read_u32_be(const char *buf)
+static inline uint32_t read_u32_be(const void *buf)
 {
        return read_portable_be(32, buf);
 }
 
-static inline uint16_t read_u16_be(const char *buf)
+static inline uint16_t read_u16_be(const void *buf)
 {
        return read_portable_be(16, buf);
 }
@@ -68,8 +66,10 @@ static inline uint16_t read_u16_be(const char *buf)
 static inline void write_portable(unsigned bits, char *buf, uint64_t val)
 {
        int i, num_bytes = bits / 8;
+       uint8_t *p = (typeof(p))buf;
+
        for (i = 0; i < num_bytes; i++) {
-               buf[i] = val & 0xff;
+               p[i] = val & 0xff;
                val = val >> 8;
        }
 }
@@ -77,43 +77,45 @@ static inline void write_portable(unsigned bits, char *buf, uint64_t val)
 static inline void write_portable_be(unsigned bits, char *buf, uint64_t val)
 {
        int i, num_bytes = bits / 8;
+       uint8_t *p = (typeof(p))buf;
+
        for (i = 0; i < num_bytes; i++) {
-               buf[num_bytes - i - 1] = val & 0xff;
+               p[num_bytes - i - 1] = val & 0xff;
                val = val >> 8;
        }
 }
 
-static inline void write_u64(char *buf, uint64_t val)
+static inline void write_u64(void *buf, uint64_t val)
 {
        write_portable(64, buf, val);
 }
 
-static inline void write_u32(char *buf, uint32_t val)
+static inline void write_u32(void *buf, uint32_t val)
 {
        write_portable(32, buf, (uint64_t) val);
 }
 
-static inline void write_u16(char *buf, uint16_t val)
+static inline void write_u16(void *buf, uint16_t val)
 {
        write_portable(16, buf, (uint64_t) val);
 }
 
-static inline void write_u8(char *buf, uint8_t val)
+static inline void write_u8(void *buf, uint8_t val)
 {
        write_portable(8, buf, (uint64_t) val);
 }
 
-static inline void write_u64_be(char *buf, uint64_t val)
+static inline void write_u64_be(void *buf, uint64_t val)
 {
        write_portable_be(64, buf, val);
 }
 
-static inline void write_u32_be(char *buf, uint32_t val)
+static inline void write_u32_be(void *buf, uint32_t val)
 {
        write_portable_be(32, buf, (uint64_t) val);
 }
 
-static inline void write_u16_be(char *buf, uint16_t val)
+static inline void write_u16_be(void *buf, uint16_t val)
 {
        write_portable_be(16, buf, (uint64_t) val);
 }
index 6078da0757f885eec2fec0dcb9fbda88cb99f454..9a801900c157e1da2da979c92d7bd60d03e21b9c 100644 (file)
@@ -1,18 +1,14 @@
-/*
- * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file prebuffer_filter.c Paraslash's prebuffering filter. */
 
 #include <regex.h>
+#include <lopsub.h>
 
 #include "para.h"
-#include "prebuffer_filter.cmdline.h"
+#include "filter_cmd.lsg.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
@@ -20,8 +16,6 @@
 
 /** Data specific to the prebuffer filter. */
 struct private_prebuffer_data {
-       /** The configuration data for this instance of the filter. */
-       struct prebuffer_filter_args_info *conf;
        /** Number of bytes prebuffered or -1 if no longer prebuffering. */
        int prebuffered;
        /** End of prebuffering period. */
@@ -34,16 +28,16 @@ static void prebuffer_pre_select(struct sched *s, void *context)
        struct btr_node *btrn = fn->btrn;
        size_t iqs = btr_get_input_queue_size(btrn);
        struct private_prebuffer_data *ppd = fn->private_data;
-       struct prebuffer_filter_args_info *conf = ppd->conf;
        struct timeval diff;
 
        if (iqs == 0)
                return;
        if (ppd->barrier.tv_sec == 0) {
+               uint32_t duration = FILTER_CMD_OPT_UINT32_VAL(PREBUFFER,
+                       DURATION, fn->lpr);
                struct timeval tv;
-               PARA_INFO_LOG("prebuffer period %dms\n",
-                       conf->duration_arg);
-               ms2tv(conf->duration_arg, &tv);
+               PARA_INFO_LOG("prebuffer period %" PRIu32 "ms\n", duration);
+               ms2tv(duration, &tv);
                tv_add(&tv, now, &ppd->barrier);
        }
        if (tv_diff(&ppd->barrier, now, &diff) < 0)
@@ -62,66 +56,37 @@ static int prebuffer_post_select(__a_unused struct sched *s, void *context)
        struct btr_node *btrn = fn->btrn;
        size_t iqs = btr_get_input_queue_size(btrn);
        struct private_prebuffer_data *ppd = fn->private_data;
-       struct prebuffer_filter_args_info *conf = ppd->conf;
+       uint32_t size = FILTER_CMD_OPT_UINT32_VAL(PREBUFFER, SIZE, fn->lpr);
+       int ret;
 
+       ret = task_get_notification(fn->task);
+       if (ret < 0)
+               goto fail;
+       ret = btr_node_status(btrn, size, BTR_NT_INTERNAL);
+       if (ret < 0)
+               goto fail;
        if (ppd->barrier.tv_sec == 0)
                return 0;
        if (tv_diff(now, &ppd->barrier, NULL) < 0)
                return 0;
-       if (iqs < conf->size_arg)
+       if (iqs < size)
                return 0;
        btr_splice_out_node(&fn->btrn);
        return -E_PREBUFFER_SUCCESS;
-}
-
-static int prebuffer_parse_config(int argc, char **argv, void **config)
-{
-       struct prebuffer_filter_args_info *conf = para_calloc(sizeof(*conf));
-       int ret;
-
-       prebuffer_filter_cmdline_parser(argc, argv, conf);
-       ret = -ERRNO_TO_PARA_ERROR(EINVAL);
-       if (conf->duration_arg < 0)
-               goto err;
-       if (conf->size_arg < 0)
-               goto err;
-       PARA_NOTICE_LOG("prebuffering %ims, %i bytes\n", conf->duration_arg,
-               conf->size_arg);
-       *config = conf;
-       return 1;
-err:
-       free(conf);
+fail:
+       btr_remove_node(&fn->btrn);
        return ret;
 }
 
 static void prebuffer_open(struct filter_node *fn)
 {
        struct private_prebuffer_data *ppd = para_calloc(sizeof(*ppd));
-
-       ppd->conf = fn->conf;
        fn->private_data = ppd;
 }
 
-static void prebuffer_free_config(void *conf)
-{
-       prebuffer_filter_cmdline_parser_free(conf);
-}
-
-/**
- * The init function of the prebuffer filter.
- *
- * \param f Pointer to the struct to initialize.
- */
-void prebuffer_filter_init(struct filter *f)
-{
-       struct prebuffer_filter_args_info dummy;
-
-       prebuffer_filter_cmdline_parser_init(&dummy);
-       f->open = prebuffer_open;
-       f->close = prebuffer_close;
-       f->parse_config = prebuffer_parse_config;
-       f->free_config = prebuffer_free_config;
-       f->pre_select = prebuffer_pre_select;
-       f->post_select = prebuffer_post_select;
-       f->help = (struct ggo_help)DEFINE_GGO_HELP(prebuffer_filter);
-}
+const struct filter lsg_filter_cmd_com_prebuffer_user_data = {
+       .open = prebuffer_open,
+       .close = prebuffer_close,
+       .pre_select = prebuffer_pre_select,
+       .post_select = prebuffer_post_select,
+};
diff --git a/recv.c b/recv.c
index 9de3033fee92925a4cd1446c8895f59055c82486..10d55d218071ccf6a314fc87d4e5f78001012db4 100644 (file)
--- a/recv.c
+++ b/recv.c
@@ -1,21 +1,18 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file recv.c the stand-alone audio stream receiver */
 
 #include <regex.h>
 #include <sys/types.h>
+#include <lopsub.h>
 
+#include "recv_cmd.lsg.h"
+#include "recv.lsg.h"
 #include "para.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "recv.h"
-#include "recv.cmdline.h"
 #include "fd.h"
 #include "string.h"
 #include "error.h"
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
-extern void afh_recv_init(struct receiver *r);
-#undef AFH_RECEIVER
-#define AFH_RECEIVER {.name = "afh", .init = afh_recv_init},
-DEFINE_RECEIVER_ARRAY;
-
-/** The gengetopt args info struct. */
-static struct recv_args_info conf;
+#define CMD_PTR (lls_cmd(0, recv_suite))
+#define OPT_RESULT(_name, _lpr) \
+       (lls_opt_result(LSG_RECV_PARA_RECV_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name, _lpr) (lls_opt_given(OPT_RESULT(_name, _lpr)))
+#define OPT_UINT32_VAL(_name, _lpr) (lls_uint32_val(0, OPT_RESULT(_name, _lpr)))
+#define OPT_STRING_VAL(_name, _lpr) (lls_string_val(0, OPT_RESULT(_name, _lpr)))
 
 static int loglevel;
 /** Always log to stderr. */
 INIT_STDERR_LOGGING(loglevel);
 
-__noreturn static void print_help_and_die(void)
+static void handle_help_flag(struct lls_parse_result *lpr)
 {
-       struct ggo_help h = DEFINE_GGO_HELP(recv);
-       bool d = conf.detailed_help_given;
-
-       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
-       print_receiver_helps(d? GPH_MODULE_FLAGS_DETAILED : GPH_MODULE_FLAGS);
-       exit(0);
+       char *help;
+
+       if (OPT_GIVEN(DETAILED_HELP, lpr))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP, lpr))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       print_receiver_helps(OPT_GIVEN(DETAILED_HELP, lpr));
+       exit(EXIT_SUCCESS);
 }
 
 /**
@@ -60,41 +62,41 @@ __noreturn static void print_help_and_die(void)
  */
 int main(int argc, char *argv[])
 {
-       int ret, r_opened = 0, receiver_num;
-       struct receiver *r = NULL;
+       int ret;
+       const struct receiver *r = NULL;
        struct receiver_node rn;
        struct stdout_task sot = {.btrn = NULL};
        static struct sched s;
        struct task_info ti;
+       const struct lls_command *cmd;
+       struct lls_parse_result *lpr; /* command line */
+       struct lls_parse_result *receiver_lpr; /* receiver specific options */
+       char *errctx;
 
-       recv_cmdline_parser(argc, argv, &conf);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       version_handle_flag("recv", conf.version_given);
-       recv_init();
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-
-       memset(&rn, 0, sizeof(struct receiver_node));
-       rn.conf = check_receiver_arg(conf.receiver_arg, &receiver_num);
-       if (!rn.conf) {
-               PARA_EMERG_LOG("invalid receiver specifier\n");
-               ret = -E_RECV_SYNTAX;
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+       if (ret < 0)
                goto out;
-       }
-       r = &receivers[receiver_num];
+       loglevel = OPT_UINT32_VAL(LOGLEVEL, lpr);
+       version_handle_flag("recv", OPT_GIVEN(VERSION, lpr));
+       handle_help_flag(lpr);
+       memset(&rn, 0, sizeof(struct receiver_node));
+       ret = check_receiver_arg(OPT_STRING_VAL(RECEIVER, lpr), &receiver_lpr);
+       if (ret < 0)
+               goto free_lpr;
+       cmd = lls_cmd(ret, recv_cmd_suite);
+       r = lls_user_data(cmd);
        rn.receiver = r;
+       rn.lpr = receiver_lpr;
        rn.btrn = btr_new_node(&(struct btr_node_description)
-               EMBRACE(.name = r->name));
+               EMBRACE(.name = lls_command_name(cmd)));
        ret = r->open(&rn);
        if (ret < 0)
-               goto out;
-       r_opened = 1;
-
+               goto remove_btrn;
        sot.btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.parent = rn.btrn, .name = "stdout"));
        stdout_task_register(&sot, &s);
 
-       ti.name = r->name;
+       ti.name = lls_command_name(cmd);
        ti.pre_select = r->pre_select;
        ti.post_select = r->post_select;
        ti.context = &rn;
@@ -104,15 +106,19 @@ int main(int argc, char *argv[])
        s.default_timeout.tv_usec = 0;
        ret = schedule(&s);
        sched_shutdown(&s);
-out:
-       if (r_opened)
-               r->close(&rn);
-       btr_remove_node(&rn.btrn);
+       r->close(&rn);
        btr_remove_node(&sot.btrn);
-       if (rn.conf)
-               r->free_config(rn.conf);
-
-       if (ret < 0)
+remove_btrn:
+       btr_remove_node(&rn.btrn);
+       lls_free_parse_result(receiver_lpr, cmd);
+free_lpr:
+       lls_free_parse_result(lpr, CMD_PTR);
+out:
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       }
        return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
 }
diff --git a/recv.h b/recv.h
index 1a0de659e92be1d6f68838c3408b8859c4278a43..36b0f1db62e348c9ab7ccdfc3b32ba656d698c5f 100644 (file)
--- a/recv.h
+++ b/recv.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file recv.h Receiver-related structures and exported symbols of recv_common.c. */
 
  */
 struct receiver_node {
        /** Points to the corresponding receiver. */
-       struct receiver *receiver;
+       const struct receiver *receiver;
        /** Receiver-specific data. */
        void *private_data;
-       /** Pointer to the configuration data for this instance. */
-       void *conf;
+       /** The parsed command line options for this instance. */
+       struct lls_parse_result *lpr;
        /** The task associated with this instance. */
        struct task *task;
        /** The receiver node is always the root of the buffer tree. */
@@ -40,48 +36,17 @@ struct receiver_node {
 /**
  * Describes one supported paraslash receiver.
  *
- * \sa http_recv.c, udp_recv.c
+ * \sa \ref http_recv.c, \ref udp_recv.c.
  */
 struct receiver {
-       /**
-        * The name of the receiver.
-        */
-       const char *name;
-       /**
-        * The receiver init function.
-        *
-        * It must fill in all other function pointers and is assumed to succeed.
-        *
-        * \sa http_recv_init udp_recv_init.
-        */
-       void (*init)(struct receiver *r);
-       /**
-        * The command line parser of the receiver.
-        *
-        * It should check whether the command line options given by \a argc
-        * and \a argv are valid.  On success, it should return a pointer to
-        * the receiver-specific configuration data determined by \a argc and
-        * \a argv.  Note that this might be called more than once with
-        * different values of \a argc and \a argv.
-        */
-       void *(*parse_config)(int argc, char **argv);
-       /**
-        * Deallocate the configuration structure of a receiver node.
-        *
-        * This calls the receiver-specific cleanup function generated by
-        * gengetopt.
-        */
-       void (*free_config)(void *conf);
        /**
         * Open one instance of the receiver.
         *
-        * This should allocate the output buffer of \a rn. and may also
-        * perform any other work necessary for retrieving the stream according
-        * to the configuration stored in the \a conf member of \a rn which is
-        * guaranteed to point to valid configuration data (as previously
-        * obtained from the config parser).
+        * This should allocate the output buffer of the given receiver node
+        * and prepare it for retrieving the audio stream according to the
+        * configuration stored in rn->lpr.
         *
-        * \sa receiver_node::conf, receiver_node::buf.
+        * \sa struct \ref receiver_node.
         */
        int (*open)(struct receiver_node *rn);
        /**
@@ -90,69 +55,59 @@ struct receiver {
         * It should free all resources associated with given receiver node
         * that were allocated during the corresponding open call.
         *
-        * \sa receiver_node.
+        * \sa \ref receiver_node.
         */
        void (*close)(struct receiver_node *rn);
        /**
         * Add file descriptors to fd_sets and compute timeout for select(2).
         *
-        * The pre_select function gets called from the driving application
-        * before entering its select loop. The receiver may use this hook to
-        * add any file descriptors to the sets of file descriptors given by \a
-        * s.
+        * If this is not NULL, the function is called in each iteration of the
+        * scheduler's select loop. The receiver may define it to add file
+        * descriptors to the file descriptor sets given by s. Those will be
+        * monitored in the subsequent call to select(2). The function may also
+        * lower the timeout value of s to make select(2) return earlier if no
+        * file descriptors are ready for I/O.
         *
-        * \sa select(2), time.c struct task, struct sched.
+        * \sa select(2), \ref time.c, struct \ref sched.
         */
        void (*pre_select)(struct sched *s, void *context);
        /**
-        * Evaluate the result from select().
+        * Evaluate the result from select(2).
         *
-        * This hook gets called after the call to select(). It should check
-        * all file descriptors which were added to any of the fd sets during
-        * the previous call to pre_select. According to the result, it may
-        * then use any non-blocking I/O to establish a connection or to
-        * receive the audio data.
+        * This is called after the call to select(2). It should check all file
+        * descriptors which were added to any of the fd sets in the previous
+        * call to ->pre_select() and perform (non-blocking) I/O operations on
+        * those fds which are ready for I/O, for example in order to establish
+        * a connection or to receive a part of the audio stream.
         *
-        * \sa select(2), struct receiver.
+        * \sa select(2), struct \ref receiver.
         */
        int (*post_select)(struct sched *s, void *context);
-
-       /** The two help texts of this receiver. */
-       struct ggo_help help;
        /**
         * Answer a buffer tree query.
         *
-        * This optional function pointer is used for inter node communications
+        * This optional function pointer allows for inter node communication
         * of the buffer tree nodes. See \ref btr_command_handler for details.
         */
        btr_command_handler execute;
 };
 
-/** Define an array of all available receivers. */
-#define DEFINE_RECEIVER_ARRAY struct receiver receivers[] = { \
-       HTTP_RECEIVER \
-       DCCP_RECEIVER \
-       UDP_RECEIVER \
-       AFH_RECEIVER \
-       {.name = NULL}};
+#define RECV_CMD(_num) (lls_cmd(_num, recv_cmd_suite))
+
+#define RECV_CMD_OPT_RESULT(_recv, _opt, _lpr) \
+       (lls_opt_result(LSG_RECV_CMD_ ## _recv ## _OPT_ ## _opt, _lpr))
+#define RECV_CMD_OPT_GIVEN(_recv, _opt, _lpr) \
+       (lls_opt_given(RECV_CMD_OPT_RESULT(_recv, _opt, _lpr)))
+#define RECV_CMD_OPT_STRING_VAL(_recv, _opt, _lpr) \
+       (lls_string_val(0, RECV_CMD_OPT_RESULT(_recv, _opt, _lpr)))
+#define RECV_CMD_OPT_UINT32_VAL(_recv, _opt, _lpr) \
+       (lls_uint32_val(0, RECV_CMD_OPT_RESULT(_recv, _opt, _lpr)))
+#define RECV_CMD_OPT_INT32_VAL(_recv, _opt, _lpr) \
+       (lls_int32_val(0, RECV_CMD_OPT_RESULT(_recv, _opt, _lpr)))
 
 /** Iterate over all available receivers. */
-#define FOR_EACH_RECEIVER(i) for (i = 0; receivers[i].name; i++)
+#define FOR_EACH_RECEIVER(i) for (i = 1; lls_cmd(i, recv_cmd_suite); i++)
 
-void recv_init(void);
-void *check_receiver_arg(char *ra, int *receiver_num);
-void print_receiver_helps(unsigned flags);
+int check_receiver_arg(const char *ra, struct lls_parse_result **lprp);
+void print_receiver_helps(bool detailed);
 int generic_recv_pre_select(struct sched *s, struct receiver_node *rn);
-
-/** \cond receiver */
-extern void http_recv_init(struct receiver *r);
-#define HTTP_RECEIVER {.name = "http", .init = http_recv_init},
-extern void dccp_recv_init(struct receiver *r);
-#define DCCP_RECEIVER {.name = "dccp", .init = dccp_recv_init},
-extern void udp_recv_init(struct receiver *r);
-#define UDP_RECEIVER {.name = "udp", .init = udp_recv_init},
-#define AFH_RECEIVER /* not active by default */
-
-extern struct receiver receivers[];
-/** \endcond receiver */
-
index 59630dfcc5f5f6fc5cfa973fd25c4476366b6b92..31fd81f1ec52c3d0b6204e8b3663a7d36e14fbb3 100644 (file)
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file recv_common.c common functions of para_recv and para_audiod */
 
 #include <regex.h>
+#include <inttypes.h>
+#include <lopsub.h>
 
+#include "recv_cmd.lsg.h"
 #include "para.h"
+#include "error.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "recv.h"
 #include "string.h"
 
-/**
- * Call the init function of each paraslash receiver.
- */
-void recv_init(void)
-{
-       int i;
-
-       FOR_EACH_RECEIVER(i)
-               receivers[i].init(&receivers[i]);
-}
-
-static void *parse_receiver_args(int receiver_num, char *options)
-{
-       struct receiver *r = &receivers[receiver_num];
-       char **argv;
-       int argc;
-       void *conf;
-
-       if (options) {
-               argc = create_shifted_argv(options, " \t", &argv);
-               if (argc < 0)
-                       return NULL;
-       } else {
-               argc = 1;
-               argv = para_malloc(2 * sizeof(char*));
-               argv[1] = NULL;
-       }
-       argv[0] = make_message("%s_recv", r->name);
-       conf = r->parse_config(argc, argv);
-       free_argv(argv);
-       return conf;
-}
-
 /**
  * Check if the given string is a valid receiver specifier.
  *
- * \param \ra string of the form receiver_name:options
- * \param receiver_num contains the number of the receiver upon success
+ * \param ra string of the form receiver_name [options...]
+ * \param lprp Filled in on success, undefined else.
  *
  * This function checks whether \a ra starts with the name of a receiver,
  * optionally followed by options for that receiver. If a valid receiver name
  * was found the remaining part of \a ra is passed to the receiver's config
  * parser.
  *
- * \return On success, a pointer to the receiver-specific gengetopt args info
- * struct is returned and \a receiver_num contains the number of the receiver.
- * On errors, the function returns \p NULL.
+ * If a NULL pointer or an empty string is passed as the first argument, the
+ * hhtp receiver with no options is assumed.
+ *
+ * \return On success the number of the receiver is returned. On errors, the
+ * function calls exit(EXIT_FAILURE).
  */
-void *check_receiver_arg(char *ra, int *receiver_num)
+int check_receiver_arg(const char *ra, struct lls_parse_result **lprp)
 {
-       int j;
+       int ret, argc, receiver_num;
+       char *errctx = NULL, **argv;
+       const struct lls_command *cmd;
 
-       PARA_DEBUG_LOG("checking %s\n", ra);
-       for (j = 0; receivers[j].name; j++) {
-               const char *name = receivers[j].name;
-               size_t len = strlen(name);
-               char c;
-               if (strlen(ra) < len)
-                       continue;
-               if (strncmp(name, ra, len))
-                       continue;
-               c = ra[len];
-               if (c && c != ' ')
-                       continue;
-               if (c && !receivers[j].parse_config)
-                       return NULL;
-               *receiver_num = j;
-               return parse_receiver_args(j, c? ra + len + 1: NULL);
+       *lprp = NULL;
+       if (!ra || !*ra) {
+               argc = 1;
+               argv = para_malloc(2 * sizeof(char*));
+               argv[0] = para_strdup("http");
+               argv[1] = NULL;
+       } else {
+               ret = create_argv(ra, " \t\n", &argv);
+               if (ret < 0) {
+                       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+                       exit(EXIT_FAILURE);
+               }
+               argc = ret;
+       }
+       ret = lls(lls_lookup_subcmd(argv[0], recv_cmd_suite, &errctx));
+       if (ret < 0) {
+               PARA_EMERG_LOG("%s: %s\n", errctx? errctx : argv[0],
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
+       receiver_num = ret;
+       cmd = RECV_CMD(receiver_num);
+       ret = lls(lls_parse(argc, argv, cmd, lprp, &errctx));
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
-       PARA_ERROR_LOG("receiver not found\n");
-       return NULL;
+       ret = receiver_num;
+       free_argv(argv);
+       return ret;
 }
 
 /**
  * Print out the help texts to all receivers.
  *
- * \param flags Passed to \ref ggo_print_help().
+ * \param detailed Whether to print the short or the detailed help.
  */
-void print_receiver_helps(unsigned flags)
+void print_receiver_helps(bool detailed)
 {
        int i;
 
-       printf_or_die("\nAvailable receivers: ");
-       FOR_EACH_RECEIVER(i)
-               printf_or_die("%s%s", i? " " : "", receivers[i].name);
-       printf_or_die("\n");
+       printf("\nAvailable receivers: ");
+       FOR_EACH_RECEIVER(i) {
+               const struct lls_command *cmd = RECV_CMD(i);
+               printf("%s%s", i? " " : "", lls_command_name(cmd));
+       }
+       printf("\n\n");
        FOR_EACH_RECEIVER(i) {
-               struct receiver *r = receivers + i;
-               if (!r->help.short_help)
+               const struct lls_command *cmd = RECV_CMD(i);
+               char *help = detailed? lls_long_help(cmd) : lls_short_help(cmd);
+               if (!help)
                        continue;
-               printf_or_die("\n%s: %s", r->name,
-                       r->help.purpose);
-               ggo_print_help(&r->help, flags);
+               printf("%s\n", help);
+               free(help);
        }
 }
 
index 84a2ee700aae4c2d4dcbe50a186ccbcf54d0cdce..bbdda51c525630c6d1411dfa547193f8ccdae9ce 100644 (file)
@@ -1,27 +1,27 @@
-/*
- * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file resample_filter.c A sample rate converter based on libsamplerate. */
 
 #include <regex.h>
 #include <samplerate.h>
+#include <lopsub.h>
 
-#include "resample_filter.cmdline.h"
+#include "filter_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
 #include "check_wav.h"
 
+#define U32_OPTVAL(_opt, _lpr) (FILTER_CMD_OPT_UINT32_VAL(RESAMPLE, _opt, _lpr))
+#define OPT_GIVEN(_opt, _lpr) (FILTER_CMD_OPT_GIVEN(RESAMPLE, _opt, _lpr))
+
+/* effective values, may differ from config arg */
 struct resample_context {
-       int channels;
+       uint32_t channels;
        int source_sample_rate;
        float ratio;
        SRC_STATE *src_state;
@@ -32,10 +32,8 @@ static int resample_execute(struct btr_node *btrn, const char *cmd, char **resul
 {
        struct filter_node *fn = btr_context(btrn);
        struct resample_context *ctx = fn->private_data;
-       struct resample_filter_args_info *conf = fn->conf;
-
-       return decoder_execute(cmd, conf->dest_sample_rate_arg, ctx->channels,
-               result);
+       uint32_t dsr = U32_OPTVAL(DEST_SAMPLE_RATE, fn->lpr);
+       return decoder_execute(cmd, dsr, ctx->channels, result);
 }
 
 static void resample_close(struct filter_node *fn)
@@ -54,13 +52,12 @@ static void resample_close(struct filter_node *fn)
 static void resample_open(struct filter_node *fn)
 {
        struct resample_context *ctx = para_calloc(sizeof(*ctx));
-       struct resample_filter_args_info *conf = fn->conf;
        struct btr_node *btrn = fn->btrn;
        struct wav_params wp;
 
        fn->private_data = ctx;
        fn->min_iqs = 2;
-       COPY_WAV_PARMS(&wp, conf);
+       LLS_COPY_WAV_PARMS(&wp, LSG_FILTER_CMD_RESAMPLE, fn->lpr);
        ctx->cwc = check_wav_init(btr_parent(btrn), btrn, &wp, NULL);
        btr_log_tree(btr_parent(btr_parent(btrn)), LL_INFO);
 }
@@ -95,18 +92,17 @@ static int resample_set_params(struct filter_node *fn)
 {
        int ret;
        struct resample_context *ctx = fn->private_data;
-       struct resample_filter_args_info *conf = fn->conf;
        struct btr_node *btrn = fn->btrn;
+       struct lls_parse_result *lpr = fn->lpr;
 
-       ctx->channels = conf->channels_arg;
-       if (!conf->channels_given) {
+       ctx->channels = U32_OPTVAL(CHANNELS, lpr);
+       if (!OPT_GIVEN(CHANNELS, lpr)) {
                ret = get_btr_val("channels", btrn);
                if (ret >= 0)
                        ctx->channels = ret;
        }
-
-       ctx->source_sample_rate = conf->sample_rate_arg;
-       if (!conf->sample_rate_given) {
+       ctx->source_sample_rate = U32_OPTVAL(SAMPLE_RATE, lpr);
+       if (!OPT_GIVEN(SAMPLE_RATE, lpr)) {
                ret = get_btr_val("sample_rate", btrn);
                if (ret >= 0)
                        ctx->source_sample_rate = ret;
@@ -114,44 +110,37 @@ static int resample_set_params(struct filter_node *fn)
        /* reject all sample formats except 16 bit signed, little endian */
        ret = get_btr_val("sample_format", btrn);
        if (ret >= 0 && ret != SF_S16_LE) {
-               const char *sample_formats[] = {SAMPLE_FORMATS};
+               const char * const sample_formats[] = {SAMPLE_FORMATS};
                PARA_ERROR_LOG("unsupported sample format: %s\n",
                        sample_formats[ret]);
                return -ERRNO_TO_PARA_ERROR(EINVAL);
        }
-       ctx->ratio = (float)conf->dest_sample_rate_arg / ctx->source_sample_rate;
+       ctx->ratio = U32_OPTVAL(DEST_SAMPLE_RATE, lpr)
+               / (float)ctx->source_sample_rate;
        return 1;
 }
 
 static int resample_init(struct filter_node *fn)
 {
-       int ret, converter;
+       int ret;
+       const uint32_t trafo[] = {
+               [RCT_BEST] = SRC_SINC_BEST_QUALITY,
+               [RCT_MEDIUM] = SRC_SINC_MEDIUM_QUALITY,
+               [RCT_FASTEST] = SRC_SINC_FASTEST,
+               [RCT_ZERO_ORDER_HOLD] = SRC_ZERO_ORDER_HOLD,
+               [RCT_LINEAR] = SRC_LINEAR
+       };
        struct resample_context *ctx = fn->private_data;
-       struct resample_filter_args_info *conf = fn->conf;
+       const struct lls_option *o_c = FILTER_CMD_OPT(RESAMPLE, CONVERTER);
+       uint32_t converter = U32_OPTVAL(CONVERTER, fn->lpr);
 
+       PARA_INFO_LOG("converter type: %s\n",
+               lls_enum_string_val(converter, o_c));
        ret = resample_set_params(fn);
        if (ret < 0)
                return ret;
-       switch (conf->converter_arg) {
-       case converter_arg_best:
-               converter = SRC_SINC_BEST_QUALITY;
-               break;
-       case converter_arg_medium:
-               converter = SRC_SINC_MEDIUM_QUALITY;
-               break;
-       case converter_arg_fastest:
-               converter = SRC_SINC_FASTEST;
-               break;
-       case converter_arg_zero_order_hold:
-               converter = SRC_ZERO_ORDER_HOLD;
-               break;
-       case converter_arg_linear:
-               converter = SRC_LINEAR;
-               break;
-       default:
-               assert(0);
-       }
-       ctx->src_state = src_new(converter, conf->channels_arg, &ret);
+       ctx->src_state = src_new(trafo[converter],
+               U32_OPTVAL(CHANNELS, fn->lpr), &ret);
        if (!ctx->src_state) {
                PARA_ERROR_LOG("%s\n", src_strerror(ret));
                return -E_LIBSAMPLERATE;
@@ -203,7 +192,6 @@ static int resample_post_select(__a_unused struct sched *s, void *context)
        int ret;
        struct filter_node *fn = context;
        struct resample_context *ctx = fn->private_data;
-       struct resample_filter_args_info *conf = fn->conf;
        struct btr_node *btrn = fn->btrn;
        int16_t *in, *out;
        size_t in_bytes, num_frames;
@@ -220,7 +208,7 @@ static int resample_post_select(__a_unused struct sched *s, void *context)
                if (ret <= 0)
                        goto out;
        }
-       if (ctx->source_sample_rate == conf->dest_sample_rate_arg) {
+       if (ctx->source_sample_rate == U32_OPTVAL(DEST_SAMPLE_RATE, fn->lpr)) {
                /*
                 * No resampling necessary. We do not splice ourselves out
                 * though, since our children might want to ask us through the
@@ -253,58 +241,45 @@ out:
        return ret;
 }
 
-static int resample_parse_config(int argc, char **argv, void **config)
+static void *resample_setup(const struct lls_parse_result *lpr)
 {
-       int ret, val, given;
-       struct resample_filter_args_info *conf = para_calloc(sizeof(*conf));
-
-       resample_filter_cmdline_parser(argc, argv, conf);
+       int given;
+       uint32_t u32;
 
        /* sanity checks */
-       ret = -ERRNO_TO_PARA_ERROR(EINVAL);
-       val = conf->channels_arg;
-       given = conf->channels_given;
-       if (val < 0 || (val == 0 && given))
-               goto err;
-       val = conf->sample_rate_arg;
-       given = conf->sample_rate_given;
-       if (val < 0 || (val == 0 && given))
-               goto err;
-       val = conf->dest_sample_rate_arg;
-       given = conf->dest_sample_rate_given;
-       if (val < 0 || (val == 0 && given))
-               goto err;
-       *config = conf;
-       return 1;
-err:
-       free(conf);
-       return ret;
+       u32 = U32_OPTVAL(CHANNELS, lpr);
+       given = OPT_GIVEN(CHANNELS, lpr);
+       if (u32 == 0 && given) {
+               PARA_EMERG_LOG("fatal: zero channels?!\n");
+               exit(EXIT_FAILURE);
+       }
+       u32 = U32_OPTVAL(SAMPLE_RATE, lpr);
+       given = OPT_GIVEN(SAMPLE_RATE, lpr);
+       if (u32 == 0 && given) {
+               PARA_EMERG_LOG("fatal: input sample rate can not be 0\n");
+               exit(EXIT_FAILURE);
+       }
+       u32 = U32_OPTVAL(DEST_SAMPLE_RATE, lpr);
+       given = OPT_GIVEN(DEST_SAMPLE_RATE, lpr);
+       if (u32 == 0 && given) {
+               PARA_EMERG_LOG("fatal: destination sample rate can not be 0\n");
+               exit(EXIT_FAILURE);
+       }
+       return NULL;
 }
 
-static void resample_free_config(void *conf)
+static void resample_teardown(__a_unused const struct lls_parse_result *lpr,
+               void *conf)
 {
-       if (!conf)
-               return;
-       resample_filter_cmdline_parser_free(conf);
        free(conf);
 }
 
-/**
- * The init function of the resample filter.
- *
- * \param f Structure to initialize.
- */
-void resample_filter_init(struct filter *f)
-{
-       struct resample_filter_args_info dummy;
-
-       resample_filter_cmdline_parser_init(&dummy);
-       f->close = resample_close;
-       f->open = resample_open;
-       f->pre_select = resample_pre_select;
-       f->post_select = resample_post_select;
-       f->parse_config = resample_parse_config;
-       f->free_config = resample_free_config;
-       f->execute = resample_execute;
-       f->help = (struct ggo_help)DEFINE_GGO_HELP(resample_filter);
-}
+const struct filter lsg_filter_cmd_com_resample_user_data = {
+       .setup = resample_setup,
+       .open = resample_open,
+       .pre_select = resample_pre_select,
+       .post_select = resample_post_select,
+       .close = resample_close,
+       .teardown = resample_teardown,
+       .execute = resample_execute
+};
index e6f03ee835a1c65bd2bdfca060dc0538f5f1e41a..76e2d7afc8ab7caea1207a36c37c8b6452af2816 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file ringbuffer.c Simple ringbuffer implementation */
 
index b422d57610e422e0d661e23b9960521be6512dbb..7a5635ca5553521c8fe75da4fc68ff58a11d6786 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file ringbuffer.h Exported symbols from ringbuffer.c. */
 
diff --git a/sched.c b/sched.c
index bc301778f9a8c20d567db31b1b19f4c413fc0ed3..a2903940fdaea1b24d6a49cfc2f54766c136070f 100644 (file)
--- a/sched.c
+++ b/sched.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file sched.c Paraslash's scheduling functions. */
 
@@ -172,7 +168,7 @@ again:
  * \param tptr Identifies the task to reap.
  *
  * This function is similar to wait(2) in that it returns information about a
- * terminated task and allows to release the resources associated with the
+ * terminated task which allows releasing the resources associated with the
  * task. Until this function is called, the terminated task remains in a zombie
  * state.
  *
@@ -387,7 +383,7 @@ void sched_min_delay(struct sched *s)
  * function does nothing. Otherwise the timeout for the next select() call is
  * set to the given value.
  *
- * \sa sched_request_timeout_ms().
+ * \sa \ref sched_request_timeout_ms().
  */
 void sched_request_timeout(struct timeval *to, struct sched *s)
 {
@@ -420,7 +416,7 @@ void sched_request_timeout_ms(long unsigned ms, struct sched *s)
  * \return If \a barrier is in the past, this function does nothing and returns
  * zero. Otherwise it returns one.
  *
- * \sa sched_request_barrier_or_min_delay().
+ * \sa \ref sched_request_barrier_or_min_delay().
  */
 int sched_request_barrier(struct timeval *barrier, struct sched *s)
 {
@@ -441,7 +437,7 @@ int sched_request_barrier(struct timeval *barrier, struct sched *s)
  * \return If \a barrier is in the past, this function requests a minimal
  * timeout and returns zero. Otherwise it returns one.
  *
- * \sa sched_min_delay(), sched_request_barrier().
+ * \sa \ref sched_min_delay(), \ref sched_request_barrier().
  */
 int sched_request_barrier_or_min_delay(struct timeval *barrier, struct sched *s)
 {
diff --git a/sched.h b/sched.h
index ada1cc106c068706fc175cc883450c17a237f4b7..35e2503e383be3611fc302f5a732e45cf322d506 100644 (file)
--- a/sched.h
+++ b/sched.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file sched.h Sched and task structures and exported symbols from sched.c. */
 
diff --git a/score.c b/score.c
index 81b3ded0021f41115dcc78a8f9605d722195c389..983589333f088b600d85d9c0271044d7a21f8eb7 100644 (file)
--- a/score.c
+++ b/score.c
@@ -1,12 +1,9 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file score.c Scoring functions to determine the audio file streaming order. */
 #include <regex.h>
 #include <osl.h>
+#include <lopsub.h>
 
 #include "para.h"
 #include "error.h"
@@ -33,8 +30,6 @@ static int ptr_compare(const struct osl_object *obj1, const struct osl_object *o
  * This function first compares the score values as usual integers. If they compare as
  * equal, the address of \a obj1 and \a obj2 are compared. So this compare function
  * returns zero if and only if \a obj1 and \a obj2 point to the same memory area.
- *
- * \sa osl_compare_function.
  */
 static int score_compare(const struct osl_object *obj1, const struct osl_object *obj2)
 {
@@ -91,8 +86,6 @@ static struct osl_table_description score_table_desc = {
  * \param num Result is returned here.
  *
  * \return Positive on success, negative on errors.
- *
- * \sa osl_get_num_rows().
  */
 int get_num_admissible_files(unsigned *num)
 {
@@ -283,7 +276,7 @@ int score_get_best(struct osl_row **aft_row, long *score)
  * \return Positive on success, negative on errors. Possible errors:
  * Errors returned by osl_get_row() and osl_del_row().
  *
- * \sa score_add(), score_shutdown().
+ * \sa \ref score_add().
  */
 int score_delete(const struct osl_row *aft_row)
 {
diff --git a/send.h b/send.h
index 0c74f0ea93208ec6b24878c963e671a4da8adb8d..f6aafbb41a2bf9b089f2ddcc9968278dea734fa1 100644 (file)
--- a/send.h
+++ b/send.h
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file send.h Sender-related defines and structures. */
 
+/**
+ * A little preprocessor fu helps to create the sender_subcommand enumeration
+ * below and the list of sender name strings without duplicating the commands.
+ */
 #define SENDER_SUBCOMMANDS \
        SENDER_SUBCOMMAND(add) /**< Add a target (udp only). */ \
        SENDER_SUBCOMMAND(delete) /**< Delete a target (udp only). */ \
        SENDER_SUBCOMMAND(on) /**< Activate the sender. */ \
        SENDER_SUBCOMMAND(off) /**< Deactivate the sender. */ \
 
+/** Concatenate "SENDER_" and the given arg and append a comma. */
 #define SENDER_SUBCOMMAND(_name) SENDER_ ## _name,
+
+/**
+ * Each sender subcommand gets an SENDER_xxx identifier. The identifier is
+ * passed from the sender command handler to the server process via shared
+ * memory.
+ */
 enum sender_subcommand {
-       SENDER_SUBCOMMANDS
+       SENDER_SUBCOMMANDS /**< List of SENDER_xxx identifiers. */
        NUM_SENDER_CMDS /**< Used as array size in struct \ref sender. */
 };
+
 #undef SENDER_SUBCOMMAND
+
+/**
+ * Redefine it to expand to the stringified name of the sender so that
+ * SENDER_SUBCOMMANDS above now expands to the comma-separated list of sender
+ * name strings. This is used in command.c to define and initialize an array of
+ * char pointers.
+ */
 #define SENDER_SUBCOMMAND(_name) #_name,
 
 /**
  * Describes one supported sender of para_server.
  *
- * \sa http_send.c udp_send.c, dccp_send.c.
+ * \sa \ref http_send.c \ref udp_send.c, \ref dccp_send.c.
  */
 struct sender {
        /** The name of the sender. */
        const char *name;
        /**
-        * The init function of this sender.
-        *
-        * It must fill in all function pointers of \a s as well as the \a
-        * client_cmds array, see below. It should also do all necessary
-        * preparations to init this sending facility, for example it could
-        * open a tcp port.
+        * Parse the command line options and initialize this sender (e.g.,
+        * initialize target or access control lists, listen on a network
+        * socket, etc.).
         */
-       void (*init)(struct sender *s);
+       void (*init)(void);
        /**
         * Return the help text of this sender.
         *
@@ -92,6 +104,8 @@ struct sender {
         * the clients aware of the end-of-file condition.
         */
        void (*shutdown_clients)(void);
+       /** Dellocate all resources. Only called on exit. */
+       void (*shutdown)(void);
        /**
         * Array of function pointers for the sender subcommands.
         *
@@ -111,6 +125,11 @@ struct sender {
        int (*resolve_target)(const char *, struct sender_command_data *);
 };
 
+/** NULL-terminated list, defined in \ref vss.c. */
+extern const struct sender * const senders[];
+/** Iterate over all senders. */
+#define FOR_EACH_SENDER(_i) for ((_i) = 0; senders[(_i)]; (_i)++)
+
 /** Describes one client, connected to a paraslash sender. */
 struct sender_client {
        /** The file descriptor of the client. */
@@ -164,10 +183,14 @@ struct fec_client_parms {
 
 /** Describes the current status of one paraslash sender. */
 struct sender_status {
-       /** The file descriptor of the socket this sender is listening on. */
-       int listen_fd;
-       /** The TCP/DCCP port used by this sender. */
-       int port;
+       /** Number of sockets to listen on, size of the two arrays below. */
+       unsigned num_listen_fds;
+       /** Derived from --http-listen-address and --dccp-listen-address. */
+       char **listen_addresses;
+       /** Default TCP/DCCP port number for addresses w/o port. */
+       int default_port;
+       /** The socket fd(s) this sender is listening on. */
+       int *listen_fds;
        /** The current number of simultaneous connections. */
        int num_clients;
        /** The maximal number of simultaneous connections. */
@@ -180,17 +203,23 @@ struct sender_status {
        struct list_head client_list;
 };
 
+/** Iterate over all listening addresses of the http/dccp sender. */
+#define FOR_EACH_LISTEN_FD(_n, _ss) for (_n = 0; _n < (_ss)->num_listen_fds; _n++)
+
 void shutdown_client(struct sender_client *sc, struct sender_status *ss);
 void shutdown_clients(struct sender_status *ss);
-void init_sender_status(struct sender_status *ss, char **access_arg, int num_access_args,
-       int port, int max_clients, int default_deny);
+void init_sender_status(struct sender_status *ss,
+               const struct lls_opt_result *acl_opt_result,
+               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);
-
 void generic_com_allow(struct sender_command_data *scd,
                struct sender_status *ss);
 void generic_com_deny(struct sender_command_data *scd,
                struct sender_status *ss);
-int generic_com_on(struct sender_status *ss, unsigned protocol);
+void generic_com_on(struct sender_status *ss, unsigned protocol);
+void generic_acl_deplete(struct list_head *acl);
 void generic_com_off(struct sender_status *ss);
 char *generic_sender_help(void);
 struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfds);
index ec7ab67108334c85085b46b84ada1db45eb91a82..ea494d9a7b23f5ee87186917d11e883631ec9cbc 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file send_common.c Functions used by more than one paraslash sender. */
 
@@ -13,6 +9,7 @@
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
 #include "para.h"
 #include "error.h"
 /** Clients will be kicked if there are more than that many bytes pending. */
 #define MAX_CQ_BYTES 40000
 
-/**
- * Open a passive socket of given layer4 type.
- *
- * Set the resulting file descriptor to nonblocking mode and add it to the list
- * of fds that are being closed in the child process when the server calls
- * fork().
- *
- * \param l4type The transport-layer protocol.
- * \param port The port number.
- *
- * \return The listening fd on success, negative on errors.
- */
-static int open_sender(unsigned l4type, int port)
-{
-       int fd, ret = para_listen_simple(l4type, port);
-
-       if (ret < 0)
-               return ret;
-       fd = ret;
-       ret = mark_fd_nonblocking(fd);
-       if (ret < 0) {
-               close(fd);
-               return ret;
-       }
-       add_close_on_fork_list(fd);
-       return fd;
-}
-
 /**
  * Shut down a client connected to a paraslash sender.
  *
@@ -71,14 +40,16 @@ static int open_sender(unsigned l4type, int port)
  * list, destroy the chunk queue of this client, delete the client from the
  * list of connected clients and free the sender_client struct.
  *
- * \sa shutdown_clients().
+ * \sa \ref shutdown_clients().
  */
 void shutdown_client(struct sender_client *sc, struct sender_status *ss)
 {
        PARA_INFO_LOG("shutting down %s on fd %d\n", sc->name, sc->fd);
        free(sc->name);
-       close(sc->fd);
-       del_close_on_fork_list(sc->fd);
+       if (!process_is_command_handler()) {
+               close(sc->fd);
+               del_close_on_fork_list(sc->fd);
+       }
        cq_destroy(sc->cq);
        list_del(&sc->node);
        free(sc->private_data);
@@ -133,24 +104,75 @@ int send_queued_chunks(int fd, struct chunk_queue *cq)
  * Initialize a struct sender status.
  *
  * \param ss The struct to initialize.
- * \param access_arg The array of access arguments given at the command line.
- * \param num_access_args The number of elements in \a access_arg.
- * \param port The tcp or dccp port to listen on.
+ * \param acl_opt_result Contains array of --{http|dccp}-access arguments.
+ * \param listen_address_opt_result Where to listen on.
+ * \param default_port Used for addresses with no specified port.
  * \param max_clients The maximal number of simultaneous connections.
  * \param default_deny Whether a blacklist should be used for access control.
  */
-void init_sender_status(struct sender_status *ss, char **access_arg,
-       int num_access_args, int port, int max_clients, int default_deny)
+void init_sender_status(struct sender_status *ss,
+               const struct lls_opt_result *acl_opt_result,
+               const struct lls_opt_result *listen_address_opt_result,
+               int default_port, int max_clients, int default_deny)
 {
-       ss->listen_fd = -1;
+       int i;
+       unsigned n = lls_opt_given(listen_address_opt_result);
+
+       if (n == 0) {
+               ss->num_listen_fds = 1;
+               ss->listen_addresses = para_malloc(sizeof(char *));
+               ss->listen_addresses[0] = NULL;
+               ss->listen_fds = para_malloc(sizeof(int));
+               ss->listen_fds[0] = -1;
+       } else {
+               ss->num_listen_fds = n;
+               ss->listen_addresses = para_malloc(n * sizeof(char *));
+               ss->listen_fds = para_malloc(n * sizeof(int));
+               FOR_EACH_LISTEN_FD(i, ss) {
+                       ss->listen_addresses[i] = para_strdup(lls_string_val(i,
+                               listen_address_opt_result));
+                       ss->listen_fds[i] = -1;
+               }
+       }
+       ss->default_port = default_port;
+
        INIT_LIST_HEAD(&ss->client_list);
-       ss->port = port;
-       acl_init(&ss->acl, access_arg, num_access_args);
+       /* Initialize an access control list */
+       INIT_LIST_HEAD(&ss->acl);
+       for (i = 0; i < lls_opt_given(acl_opt_result); i++) {
+               const char *arg = lls_string_val(i, acl_opt_result);
+               char addr[16];
+               int mask;
+               if (!parse_cidr(arg, addr, sizeof(addr), &mask))
+                       PARA_WARNING_LOG("ACL syntax error: %s, ignoring\n",
+                               arg);
+               else
+                       acl_add_entry(&ss->acl, addr, mask);
+       }
        ss->num_clients = 0;
        ss->max_clients = max_clients;
        ss->default_deny = default_deny;
 }
 
+/**
+ * Deallocate all resources allocated in \ref init_sender_status().
+ *
+ * \param ss The structure whose components should be freed.
+ *
+ * This frees the dynamically allocated parts of the structure which was
+ * initialized by an earlier call to \ref init_sender_status(). It does *not*
+ * call free(ss), though.
+ */
+void free_sender_status(const struct sender_status *ss)
+{
+       int i;
+
+       free(ss->listen_fds);
+       FOR_EACH_LISTEN_FD(i, ss)
+               free(ss->listen_addresses[i]);
+       free(ss->listen_addresses);
+}
+
 /**
  * Return a string containing the current status of a sender.
  *
@@ -161,24 +183,35 @@ void init_sender_status(struct sender_status *ss, char **access_arg,
  */
 char *generic_sender_status(struct sender_status *ss, const char *name)
 {
-       char *clnts = NULL, *ret;
+       char *clnts = NULL, *ret, *addr = NULL;
        struct sender_client *sc, *tmp_sc;
-
+       unsigned n;
        char *acl_contents = acl_get_contents(&ss->acl);
+
        list_for_each_entry_safe(sc, tmp_sc, &ss->client_list, node) {
                char *tmp = make_message("%s%s ", clnts? clnts : "", sc->name);
                free(clnts);
                clnts = tmp;
        }
+       FOR_EACH_LISTEN_FD(n, ss) {
+               char *url = format_url(ss->listen_addresses[n], ss->default_port);
+               char *tmp = make_message("%s%s%s (fd %d)", addr?
+                       addr : "", addr? ", " : "", url,
+                       ss->listen_fds[n]);
+               free(url);
+               free(addr);
+               addr = tmp;
+       }
        ret = make_message(
-               "status: %s\n"
-               "port: %s\n"
+               "listening address(es): %s\n"
+               "default port: %s\n"
                "number of connected clients: %d\n"
                "maximal number of clients: %d%s\n"
                "connected clients: %s\n"
                "access %s list: %s\n",
-               (ss->listen_fd >= 0)? "on" : "off",
-               stringify_port(ss->port, strcmp(name, "http") ? "dccp" : "tcp"),
+               addr,
+               stringify_port(ss->default_port,
+                       strcmp(name, "http")? "dccp" : "tcp"),
                ss->num_clients,
                ss->max_clients,
                ss->max_clients > 0? "" : " (unlimited)",
@@ -197,7 +230,7 @@ char *generic_sender_status(struct sender_status *ss, const char *name)
  * \param scd Contains the IP and the netmask.
  * \param ss The sender.
  *
- * \sa generic_com_deny().
+ * \sa \ref generic_com_deny().
  */
 void generic_com_allow(struct sender_command_data *scd,
                struct sender_status *ss)
@@ -205,13 +238,29 @@ void generic_com_allow(struct sender_command_data *scd,
        acl_allow(scd->host, scd->netmask, &ss->acl, ss->default_deny);
 }
 
+/**
+ * Empty the access control list of a sender.
+ *
+ * \param acl The access control list of the sender.
+ *
+ * This is called from the ->shutdown methods of the http and the dccp sender.
+ */
+void generic_acl_deplete(struct list_head *acl)
+{
+       /*
+        * Since default_deny is false, the ACL is considered a blacklist. A
+        * netmask of zero matches any IP address, so this call empties the ACL.
+        */
+       acl_allow("0.0.0.0", 0 /* netmask */, acl, 0 /* default_deny */);
+}
+
 /**
  * Deny connections from the given range of IP addresses.
  *
  * \param scd see \ref generic_com_allow().
  * \param ss see \ref generic_com_allow().
  *
- * \sa generic_com_allow().
+ * \sa \ref generic_com_allow().
  */
 void generic_com_deny(struct sender_command_data *scd,
                struct sender_status *ss)
@@ -223,21 +272,48 @@ void generic_com_deny(struct sender_command_data *scd,
  * Activate a paraslash sender.
  *
  * \param ss The sender to activate.
- * \param protocol The symbolic name of the transport-layer protocol.
+ * \param protocol layer4 type (IPPROTO_TCP or IPPROTO_DCCP).
  *
- * \return Standard.
+ * This opens a passive socket of given layer4 type, sets the resulting file
+ * descriptor to nonblocking mode and adds it to the close on fork list.
+ *
+ * Errors are logged but otherwise ignored.
  */
-int generic_com_on(struct sender_status *ss, unsigned protocol)
+void generic_com_on(struct sender_status *ss, unsigned protocol)
 {
        int ret;
+       unsigned n;
 
-       if (ss->listen_fd >= 0)
-               return 1;
-       ret = open_sender(protocol, ss->port);
-       if (ret < 0)
-               return ret;
-       ss->listen_fd = ret;
-       return 1;
+       FOR_EACH_LISTEN_FD(n, ss) {
+               if (ss->listen_fds[n] >= 0)
+                       continue;
+               ret = para_listen(protocol, ss->listen_addresses[n],
+                       ss->default_port);
+               if (ret < 0) {
+                       char *url = format_url(ss->listen_addresses[n],
+                               ss->default_port);
+                       PARA_ERROR_LOG("could not listen on %s %s: %s\n",
+                               protocol == IPPROTO_TCP? "TCP" : "DCCP",
+                               url, para_strerror(-ret));
+                       free(url);
+                       continue;
+               }
+               ss->listen_fds[n] = ret;
+               ret = mark_fd_nonblocking(ss->listen_fds[n]);
+               if (ret < 0) {
+                       char *url = format_url(ss->listen_addresses[n],
+                               ss->default_port);
+                       PARA_ERROR_LOG("could not set %s socket fd for %s to "
+                               "nonblocking mode: %s\n",
+                               protocol == IPPROTO_TCP? "TCP" : "DCCP", url,
+                               para_strerror(-ret));
+                       free(url);
+                       close(ss->listen_fds[n]);
+                       ss->listen_fds[n] = -1;
+                       continue;
+               }
+               add_close_on_fork_list(ss->listen_fds[n]);
+       }
 }
 
 /**
@@ -247,28 +323,30 @@ int generic_com_on(struct sender_status *ss, unsigned protocol)
  *
  * \param ss The sender to deactivate.
  *
- * \sa \ref del_close_on_fork_list(), shutdown_clients().
+ * \sa \ref del_close_on_fork_list(), \ref shutdown_clients().
  */
 void generic_com_off(struct sender_status *ss)
 {
-       if (ss->listen_fd < 0)
-               return;
-       PARA_NOTICE_LOG("closing port %d\n", ss->port);
-       close(ss->listen_fd);
-       del_close_on_fork_list(ss->listen_fd);
-       shutdown_clients(ss);
-       ss->listen_fd = -1;
+       unsigned n;
+
+       FOR_EACH_LISTEN_FD(n, ss) {
+               if (ss->listen_fds[n] < 0)
+                       return;
+               close(ss->listen_fds[n]);
+               del_close_on_fork_list(ss->listen_fds[n]);
+               shutdown_clients(ss);
+               ss->listen_fds[n] = -1;
+       }
 }
 
 /**
- * Accept a connection on the socket this server is listening on.
+ * Accept a connection on the socket(s) this server is listening on.
  *
  * \param ss The sender whose listening fd is ready for reading.
  * \param rfds Passed to para_accept(),
  *
- * This must be called only if the socket fd of \a ss is ready for reading.  It
- * calls para_accept() to accept the connection and performs the following
- * actions on the resulting file descriptor \a fd:
+ * This accepts incoming connections on any of the listening sockets of the
+ * server. If there is a connection pending, the function
  *
  *     - Checks whether the maximal number of connections are exceeded.
  *     - Sets \a fd to nonblocking mode.
@@ -293,36 +371,40 @@ struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfd
 {
        struct sender_client *sc;
        int fd, ret;
+       unsigned n;
 
-       if (ss->listen_fd < 0)
-               return NULL;
-       ret = para_accept(ss->listen_fd, rfds, NULL, 0, &fd);
-       if (ret < 0)
-               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-       if (ret <= 0)
-               return NULL;
-       ret = -E_MAX_CLIENTS;
-       if (ss->max_clients > 0 && ss->num_clients >= ss->max_clients)
-               goto err_out;
-       ret = mark_fd_nonblocking(fd);
-       if (ret < 0)
-               goto err_out;
-       ret = acl_check_access(fd, &ss->acl, ss->default_deny);
-       if (ret < 0)
-               goto err_out;
-       ss->num_clients++;
-       sc = para_calloc(sizeof(*sc));
-       sc->fd = fd;
-       sc->name = para_strdup(remote_name(fd));
-       sc->cq = cq_new(MAX_CQ_BYTES);
-       para_list_add(&sc->node, &ss->client_list);
-       add_close_on_fork_list(fd);
-       PARA_INFO_LOG("accepted client #%d: %s (fd %d)\n", ss->num_clients,
-               sc->name, fd);
-       return sc;
-err_out:
-       PARA_WARNING_LOG("%s\n", para_strerror(-ret));
-       close(fd);
+       FOR_EACH_LISTEN_FD(n, ss) {
+               if (ss->listen_fds[n] < 0)
+                       continue;
+               ret = para_accept(ss->listen_fds[n], rfds, NULL, 0, &fd);
+               if (ret < 0)
+                       goto warn;
+               if (ret == 0)
+                       continue;
+               ret = -E_MAX_CLIENTS;
+               if (ss->max_clients > 0 && ss->num_clients >= ss->max_clients)
+                       goto close_fd_and_warn;
+               ret = mark_fd_nonblocking(fd);
+               if (ret < 0)
+                       goto close_fd_and_warn;
+               ret = acl_check_access(fd, &ss->acl, ss->default_deny);
+               if (ret < 0)
+                       goto close_fd_and_warn;
+               ss->num_clients++;
+               sc = para_calloc(sizeof(*sc));
+               sc->fd = fd;
+               sc->name = para_strdup(remote_name(fd));
+               sc->cq = cq_new(MAX_CQ_BYTES);
+               para_list_add(&sc->node, &ss->client_list);
+               add_close_on_fork_list(fd);
+               PARA_INFO_LOG("accepted client #%d: %s (fd %d)\n", ss->num_clients,
+                       sc->name, fd);
+               return sc;
+close_fd_and_warn:
+               close(fd);
+warn:
+               PARA_WARNING_LOG("%s\n", para_strerror(-ret));
+       }
        return NULL;
 }
 
index af93941dd6dba2a56fc2cb63d23162f9355c46fb..09087f7a72396bf01e51782dbebc1b9d1a82eb7c 100644 (file)
--- a/server.c
+++ b/server.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file server.c Paraslash's main server. */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "server.lsg.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "crypt.h"
-#include "server.cmdline.h"
 #include "afh.h"
 #include "string.h"
 #include "afs.h"
+#include "net.h"
 #include "server.h"
 #include "list.h"
 #include "send.h"
 #include "vss.h"
 #include "config.h"
 #include "close_on_fork.h"
-#include "net.h"
 #include "daemon.h"
 #include "ipc.h"
 #include "fd.h"
 #include "signal.h"
 #include "user_list.h"
 #include "color.h"
-#include "ggo.h"
 #include "version.h"
 
 /** Array of error strings. */
@@ -80,29 +77,64 @@ __printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log;
 struct misc_meta_data *mmd;
 
 /**
- * The configuration of para_server
+ * The active value for all config options of para_server.
  *
- * It also contains the options for the audio file selector, audio format
- * handler and all supported senders.
+ * It is computed by merging the parse result of the command line options with
+ * the parse result of the config file.
  */
-struct server_args_info conf;
+struct lls_parse_result *server_lpr = NULL;
+
+/* Command line options (no config file options). Used in handle_sighup(). */
+static struct lls_parse_result *cmdline_lpr;
 
-/** A random value used in child context for authentication. */
+/**
+ * A random number used to "authenticate" the afs connection.
+ *
+ * para_server picks this number by random before it forks the afs process. The
+ * command handlers know this number as well and write it to the afs socket,
+ * together with the id of the shared memory area which contains the payload of
+ * the afs command. A local process has to know this number to abuse the afs
+ * service provided by the local socket.
+ */
 uint32_t afs_socket_cookie;
 
 /** The mutex protecting the shared memory area containing the mmd struct. */
 int mmd_mutex;
 
-/** The file containing user information (public key, permissions). */
-static char *user_list_file = NULL;
+/* Serializes log output. */
+static int log_mutex;
 
 static struct sched sched;
 static struct signal_task *signal_task;
 
+/** The process id of the audio file selector process. */
+pid_t afs_pid = 0;
+
+/* The main server process (parent of afs and the command handlers). */
+static pid_t server_pid;
+
+/**
+ * Tell whether the executing process is a command handler.
+ *
+ * Cleanup on exit must be performed differently for command handlers.
+ *
+ * \return True if the pid of the executing process is neither the server pid
+ * nor the afs pid.
+ */
+bool process_is_command_handler(void)
+{
+       pid_t pid = getpid();
+
+       return pid != afs_pid && pid != server_pid;
+}
+
 /** The task responsible for server command handling. */
 struct server_command_task {
-       /** TCP port on which para_server listens for connections. */
-       int listen_fd;
+       unsigned num_listen_fds; /* only one by default */
+       /** TCP socket(s) on which para_server listens for connections. */
+       int *listen_fds;
+       /* File descriptor for the accepted socket. */
+       int child_fd;
        /** Copied from para_server's main function. */
        int argc;
        /** Argument vector passed to para_server's main function. */
@@ -124,9 +156,17 @@ char *server_get_tasks(void)
        return get_task_list(&sched);
 }
 
-/*
- * setup shared memory area and get mutex for locking
- */
+static void pre_log_hook(void)
+{
+       mutex_lock(log_mutex);
+}
+
+static void post_log_hook(void)
+{
+       mutex_unlock(log_mutex);
+}
+
+/* Setup shared memory area and init mutexes */
 static void init_ipc_or_die(void)
 {
        void *shm;
@@ -145,6 +185,10 @@ static void init_ipc_or_die(void)
        if (ret < 0)
                goto err_out;
        mmd_mutex = ret;
+       ret = mutex_new();
+       if (ret < 0)
+               goto destroy_mmd_mutex;
+       log_mutex = ret;
 
        mmd->num_played = 0;
        mmd->num_commands = 0;
@@ -154,6 +198,8 @@ static void init_ipc_or_die(void)
        mmd->vss_status_flags = VSS_NEXT;
        mmd->new_vss_status_flags = VSS_NEXT;
        return;
+destroy_mmd_mutex:
+       mutex_destroy(mmd_mutex);
 err_out:
        PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        exit(EXIT_FAILURE);
@@ -162,71 +208,59 @@ err_out:
 /**
  * (Re-)read the server configuration files.
  *
- * \param override Passed to gengetopt to activate the override feature.
+ * \param reload Whether config file overrides command line.
  *
- * This function also re-opens the logfile and sets the global \a
- * user_list_file variable.
+ * This function also re-opens the logfile and the user list. On SIGHUP it is
+ * called from both server and afs context.
  */
-void parse_config_or_die(int override)
+void parse_config_or_die(bool reload)
 {
-       char *home = para_homedir();
        int ret;
-       char *cf;
-
-       daemon_close_log();
-       if (conf.config_file_given)
-               cf = para_strdup(conf.config_file_arg);
-       else
-               cf = make_message("%s/.paraslash/server.conf", home);
-       free(user_list_file);
-       if (!conf.user_list_given)
-               user_list_file = make_message("%s/.paraslash/server.users", home);
-       else
-               user_list_file = para_strdup(conf.user_list_arg);
-       ret = file_exists(cf);
-       if (conf.config_file_given && !ret)  {
-               ret = -1;
-               PARA_EMERG_LOG("can not read config file %s\n", cf);
-               goto out;
-       }
-       if (ret) {
-               int tmp = conf.daemon_given;
-               struct server_cmdline_parser_params params = {
-                       .override = override,
-                       .initialize = 0,
-                       .check_required = 1,
-                       .check_ambiguity = 0,
-                       .print_errors = !conf.daemon_given
-               };
-               server_cmdline_parser_config_file(cf, &conf, &params);
-               daemon_set_loglevel(conf.loglevel_arg);
-               conf.daemon_given = tmp;
+       unsigned flags = MCF_DONT_FREE;
+
+       if (server_lpr != cmdline_lpr)
+               lls_free_parse_result(server_lpr, CMD_PTR);
+       server_lpr = cmdline_lpr;
+       if (reload)
+               flags |= MCF_OVERRIDE;
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "server.conf", &server_lpr, CMD_PTR, server_suite, flags);
+       if (ret < 0) {
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
-       if (conf.logfile_given) {
-               daemon_set_logfile(conf.logfile_arg);
+       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       if (OPT_GIVEN(LOGFILE)) {
+               daemon_set_logfile(OPT_STRING_VAL(LOGFILE));
                daemon_open_log_or_die();
        }
-
-       if (daemon_init_colors_or_die(conf.color_arg, color_arg_auto, color_arg_no,
-                       conf.logfile_given)) {
+       if (daemon_init_colors_or_die(OPT_UINT32_VAL(COLOR), COLOR_AUTO,
+                       COLOR_NO, OPT_GIVEN(LOGFILE))) {
                int i;
-               for (i = 0; i < conf.log_color_given; i++)
-                       daemon_set_log_color_or_die(conf.log_color_arg[i]);
+               for (i = 0; i < OPT_GIVEN(LOG_COLOR); i++)
+                       daemon_set_log_color_or_die(lls_string_val(i,
+                               OPT_RESULT(LOG_COLOR)));
        }
        daemon_set_flag(DF_LOG_PID);
        daemon_set_flag(DF_LOG_LL);
        daemon_set_flag(DF_LOG_TIME);
-       if (conf.log_timing_given)
+       if (OPT_GIVEN(LOG_TIMING))
                daemon_set_flag(DF_LOG_TIMING);
-       ret = 1;
-out:
-       free(cf);
-       free(home);
-       if (ret > 0)
-               return;
-       free(user_list_file);
-       user_list_file = NULL;
-       exit(EXIT_FAILURE);
+       daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
+       if (!reload || getpid() != afs_pid) {
+               char *user_list_file;
+               if (OPT_GIVEN(USER_LIST))
+                       user_list_file = para_strdup(OPT_STRING_VAL(USER_LIST));
+               else {
+                       char *home = para_homedir();
+                       user_list_file = make_message("%s/.paraslash/server.users", home);
+                       free(home);
+               }
+               user_list_init(user_list_file);
+               free(user_list_file);
+       }
+       return;
 }
 
 /*
@@ -234,17 +268,21 @@ out:
  */
 static void handle_sighup(void)
 {
+
        PARA_NOTICE_LOG("SIGHUP\n");
-       parse_config_or_die(1); /* reopens log */
-       init_user_list(user_list_file); /* reload user list */
-       if (mmd->afs_pid)
-               kill(mmd->afs_pid, SIGHUP);
+       parse_config_or_die(true);
+       if (afs_pid != 0)
+               kill(afs_pid, SIGHUP);
 }
 
 static int signal_post_select(struct sched *s, __a_unused void *context)
 {
-       int signum = para_next_signal(&s->rfds);
+       int ret, signum;
 
+       ret = task_get_notification(signal_task->task);
+       if (ret < 0)
+               return ret;
+       signum = para_next_signal(&s->rfds);
        switch (signum) {
        case 0:
                return 0;
@@ -254,43 +292,37 @@ static int signal_post_select(struct sched *s, __a_unused void *context)
        case SIGCHLD:
                for (;;) {
                        pid_t pid;
-                       int ret = para_reap_child(&pid);
+                       ret = para_reap_child(&pid);
                        if (ret <= 0)
                                break;
-                       if (pid != mmd->afs_pid)
+                       if (pid != afs_pid)
                                continue;
                        PARA_EMERG_LOG("fatal: afs died\n");
-                       kill(0, SIGTERM);
-                       goto cleanup;
+                       goto genocide;
                }
                break;
        /* die on sigint/sigterm. Kill all children too. */
        case SIGINT:
        case SIGTERM:
                PARA_EMERG_LOG("terminating on signal %d\n", signum);
+genocide:
                kill(0, SIGTERM);
                /*
-                * We must wait for afs because afs catches SIGINT/SIGTERM.
-                * Before reacting to the signal, afs might want to use the
+                * We must wait for all of our children to die. For the afs
+                * process or a command handler might want to use the
                 * shared memory area and the mmd mutex.  If we destroy this
                 * mutex too early and afs tries to lock the shared memory
                 * area, the call to mutex_lock() will fail and terminate the
                 * afs process. This leads to dirty osl tables.
-                *
-                * There's no such problem with the other children of the
-                * server process (the command handlers) as these reset their
-                * SIGINT/SIGTERM handlers to the default action, i.e.  these
-                * processes get killed immediately by the above kill().
                 */
-               PARA_INFO_LOG("waiting for afs (pid %d) to die\n",
-                       (int)mmd->afs_pid);
-               waitpid(mmd->afs_pid, NULL, 0);
-cleanup:
+               PARA_INFO_LOG("waiting for child processes to die\n");
+               mutex_unlock(mmd_mutex);
+               while (wait(NULL) != -1 || errno != ECHILD)
+                       ; /* still at least one child alive */
+               mutex_lock(mmd_mutex);
                free(mmd->afd.afhi.chunk_table);
-               close_listed_fds();
-               mutex_destroy(mmd_mutex);
-               shm_detach(mmd);
-               exit(EXIT_FAILURE);
+               task_notify_all(s, E_DEADLY_SIGNAL);
+               return -E_DEADLY_SIGNAL;
        }
        return 0;
 }
@@ -315,20 +347,22 @@ static void init_signal_task(void)
 
 static void command_pre_select(struct sched *s, void *context)
 {
+       unsigned n;
        struct server_command_task *sct = context;
-       para_fd_set(sct->listen_fd, &s->rfds, &s->max_fileno);
+
+       for (n = 0; n < sct->num_listen_fds; n++)
+               para_fd_set(sct->listen_fds[n], &s->rfds, &s->max_fileno);
 }
 
-static int command_post_select(struct sched *s, void *context)
+static int command_task_accept(unsigned listen_idx, struct sched *s,
+               struct server_command_task *sct)
 {
-       struct server_command_task *sct = context;
-
        int new_fd, ret, i;
        char *peer_name;
        pid_t child_pid;
        uint32_t *chunk_table;
 
-       ret = para_accept(sct->listen_fd, &s->rfds, NULL, 0, &new_fd);
+       ret = para_accept(sct->listen_fds[listen_idx], &s->rfds, NULL, 0, &new_fd);
        if (ret <= 0)
                goto out;
        mmd->num_connects++;
@@ -360,47 +394,106 @@ static int command_post_select(struct sched *s, void *context)
        PARA_INFO_LOG("accepted connection from %s\n", peer_name);
        /* mmd might already have changed at this point */
        free(chunk_table);
-       alarm(ALARM_TIMEOUT);
-       close_listed_fds();
-       signal_shutdown(signal_task);
+       sct->child_fd = new_fd;
        /*
         * put info on who we are serving into argv[0] to make
         * client ip visible in top/ps
         */
        for (i = sct->argc - 1; i >= 0; i--)
                memset(sct->argv[i], 0, strlen(sct->argv[i]));
-       sprintf(sct->argv[0], "para_server (serving %s)", peer_name);
-       handle_connect(new_fd, peer_name);
-       /* never reached*/
+       i = sct->argc - 1 - lls_num_inputs(cmdline_lpr);
+       sprintf(sct->argv[i], "para_server (serving %s)", peer_name);
+       /* ask other tasks to terminate */
+       task_notify_all(s, E_CHILD_CONTEXT);
+       /*
+        * After we return, the scheduler calls server_select() with a minimal
+        * timeout value, because the remaining tasks have a notification
+        * pending. Next it calls the ->post_select method of these tasks,
+        * which will return negative in view of the notification. This causes
+        * schedule() to return as there are no more runnable tasks.
+        *
+        * Note that semaphores are not inherited across a fork(), so we don't
+        * hold the lock at this point. Since server_select() drops the lock
+        * prior to calling para_select(), we need to acquire it here.
+        */
+       mutex_lock(mmd_mutex);
+       return -E_CHILD_CONTEXT;
 out:
        if (ret < 0)
                PARA_CRIT_LOG("%s\n", para_strerror(-ret));
        return 0;
 }
 
-static void init_server_command_task(int argc, char **argv)
+static int command_post_select(struct sched *s, void *context)
 {
+       struct server_command_task *sct = context;
+       unsigned n;
        int ret;
-       static struct server_command_task server_command_task_struct,
-               *sct = &server_command_task_struct;
+
+       ret = task_get_notification(sct->task);
+       if (ret < 0)
+               return ret;
+       for (n = 0; n < sct->num_listen_fds; n++) {
+               ret = command_task_accept(n, s, sct);
+               if (ret < 0) {
+                       free(sct->listen_fds);
+                       return ret;
+               }
+       }
+       return 0;
+}
+
+static void init_server_command_task(struct server_command_task *sct,
+               int argc, char **argv)
+{
+       int ret;
+       unsigned n;
+       uint32_t port = OPT_UINT32_VAL(PORT);
 
        PARA_NOTICE_LOG("initializing tcp command socket\n");
+       sct->child_fd = -1;
        sct->argc = argc;
        sct->argv = argv;
-       ret = para_listen_simple(IPPROTO_TCP, conf.port_arg);
-       if (ret < 0)
-               goto err;
-       sct->listen_fd = ret;
-       ret = mark_fd_nonblocking(sct->listen_fd);
-       if (ret < 0)
-               goto err;
-       add_close_on_fork_list(sct->listen_fd); /* child doesn't need the listener */
+       if (!OPT_GIVEN(LISTEN_ADDRESS)) {
+               sct->num_listen_fds = 1;
+               sct->listen_fds = para_malloc(sizeof(int));
+               ret = para_listen_simple(IPPROTO_TCP, port);
+               if (ret < 0)
+                       goto err;
+               sct->listen_fds[0] = ret;
+       } else {
+               sct->num_listen_fds = OPT_GIVEN(LISTEN_ADDRESS);
+               sct->listen_fds = para_malloc(sct->num_listen_fds * sizeof(int));
+               for (n = 0; n < OPT_GIVEN(LISTEN_ADDRESS); n++) {
+                       const char *arg;
+                       arg = lls_string_val(n, OPT_RESULT(LISTEN_ADDRESS));
+                       ret = para_listen(IPPROTO_TCP, arg, port);
+                       if (ret < 0)
+                               goto err;
+                       sct->listen_fds[n] = ret;
+               }
+       }
+       for (n = 0; n < sct->num_listen_fds; n++) {
+               ret = mark_fd_nonblocking(sct->listen_fds[n]);
+               if (ret < 0)
+                       goto err;
+               /* child doesn't need the listener */
+               add_close_on_fork_list(sct->listen_fds[n]);
+       }
+
        sct->task = task_register(&(struct task_info) {
                .name = "server command",
                .pre_select = command_pre_select,
                .post_select = command_post_select,
                .context = sct,
        }, &sched);
+       /*
+        * Detect whether the abstract Unix domain socket space is supported,
+        * but do not create the socket. We check this once in server context
+        * so that the command handlers inherit this bit of information and
+        * don't need to check again.
+        */
+       create_local_socket(NULL);
        return;
 err:
        PARA_EMERG_LOG("%s\n", para_strerror(-ret));
@@ -410,7 +503,6 @@ err:
 static int init_afs(int argc, char **argv)
 {
        int ret, afs_server_socket[2];
-       pid_t afs_pid;
        char c;
 
        ret = socketpair(PF_UNIX, SOCK_STREAM, 0, afs_server_socket);
@@ -424,13 +516,16 @@ static int init_afs(int argc, char **argv)
        if (afs_pid == 0) { /* child (afs) */
                int i;
 
+               afs_pid = getpid();
+               crypt_shutdown();
+               user_list_deplete();
                for (i = argc - 1; i >= 0; i--)
                        memset(argv[i], 0, strlen(argv[i]));
-               sprintf(argv[0], "para_server (afs)");
+               i = argc - lls_num_inputs(cmdline_lpr) - 1;
+               sprintf(argv[i], "para_server (afs)");
                close(afs_server_socket[0]);
-               afs_init(afs_socket_cookie, afs_server_socket[1]);
+               afs_init(afs_server_socket[1]);
        }
-       mmd->afs_pid = afs_pid;
        close(afs_server_socket[1]);
        if (read(afs_server_socket[0], &c, 1) <= 0) {
                PARA_EMERG_LOG("early afs exit\n");
@@ -445,70 +540,68 @@ static int init_afs(int argc, char **argv)
        return afs_server_socket[0];
 }
 
-__noreturn static void print_help_and_die(void)
+static void handle_help_flags(void)
 {
-       struct ggo_help h = DEFINE_GGO_HELP(server);
-       bool d = conf.detailed_help_given;
+       char *help;
+       bool d = OPT_GIVEN(DETAILED_HELP);
 
-       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
-       exit(0);
+       if (d)
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       exit(EXIT_SUCCESS);
 }
 
-static void server_init(int argc, char **argv)
+static void server_init(int argc, char **argv, struct server_command_task *sct)
 {
-       struct server_cmdline_parser_params params = {
-               .override = 0,
-               .initialize = 1,
-               .check_required = 0,
-               .check_ambiguity = 0,
-               .print_errors = 1
-       };
-       int afs_socket, daemon_pipe = -1;
+       int ret, afs_socket, daemon_pipe = -1;
+       char *errctx;
 
        valid_fd_012();
-       init_random_seed_or_die();
        /* parse command line options */
-       server_cmdline_parser_ext(argc, argv, &conf, &params);
-       daemon_set_loglevel(conf.loglevel_arg);
-       version_handle_flag("server", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-       daemon_set_priority(conf.priority_arg);
-       daemon_drop_privileges_or_die(conf.user_arg, conf.group_arg);
-       /* parse config file, open log and set defaults */
-       parse_config_or_die(0);
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &cmdline_lpr, &errctx));
+       if (ret < 0)
+               goto fail;
+       server_lpr = cmdline_lpr;
+       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_drop_privileges_or_die(OPT_STRING_VAL(USER),
+               OPT_STRING_VAL(GROUP));
+       version_handle_flag("server", OPT_GIVEN(VERSION));
+       handle_help_flags();
+       parse_config_or_die(false);
+       /* become daemon */
+       if (OPT_GIVEN(DAEMON))
+               daemon_pipe = daemonize(true /* parent waits for SIGTERM */);
+       server_pid = getpid();
+       crypt_init();
        daemon_log_welcome("server");
-       init_ipc_or_die(); /* init mmd struct and mmd->lock */
+       init_ipc_or_die(); /* init mmd struct, mmd and log mutex */
        daemon_set_start_time();
-       init_user_list(user_list_file);
-       /* become daemon */
-       if (conf.daemon_given)
-               daemon_pipe = daemonize(true /* parent waits for us */);
-       PARA_NOTICE_LOG("initializing audio format handlers\n");
-       afh_init();
-
+       daemon_set_hooks(pre_log_hook, post_log_hook);
        /*
         * Although afs uses its own signal handling we must ignore SIGUSR1
         * _before_ the afs child process gets born by init_afs() below.  It's
         * racy to do this in the child because the parent might send SIGUSR1
-        * before the child gets a chance to ignore this signal -- only the
-        * good die young.
-        */
-       para_sigaction(SIGUSR1, SIG_IGN);
-       /*
-        * We have to block SIGCHLD before the afs process is being forked off.
-        * Otherwise, para_server does not notice if afs dies before the
+        * before the child gets a chance to ignore this signal.
+        *
+        * We also have to block SIGCHLD before the afs process is created
+        * because otherwise para_server does not notice if afs dies before the
         * SIGCHLD handler has been installed for the parent process by
         * init_signal_task() below.
         */
+       para_sigaction(SIGUSR1, SIG_IGN);
        para_block_signal(SIGCHLD);
        PARA_NOTICE_LOG("initializing the audio file selector\n");
        afs_socket = init_afs(argc, argv);
        init_signal_task();
        para_unblock_signal(SIGCHLD);
        PARA_NOTICE_LOG("initializing virtual streaming system\n");
-       init_vss_task(afs_socket, &sched);
-       init_server_command_task(argc, argv);
+       vss_init(afs_socket, &sched);
+       init_server_command_task(sct, argc, argv);
        if (daemon_pipe >= 0) {
                if (write(daemon_pipe, "\0", 1) < 0) {
                        PARA_EMERG_LOG("daemon_pipe: %s", strerror(errno));
@@ -517,6 +610,13 @@ static void server_init(int argc, char **argv)
                close(daemon_pipe);
        }
        PARA_NOTICE_LOG("server init complete\n");
+       return;
+fail:
+       assert(ret < 0);
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", errctx);
+       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       exit(EXIT_FAILURE);
 }
 
 static void status_refresh(void)
@@ -553,6 +653,21 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds,
        return ret;
 }
 
+/**
+ * Deallocate all lopsub parse results.
+ *
+ * The server allocates a parse result for command line options and optionally
+ * a second parse result for the effective configuration, defined by merging
+ * the command line options with the options stored in the configuration file.
+ * This function frees both structures.
+ */
+void free_lpr(void)
+{
+       lls_free_parse_result(server_lpr, CMD_PTR);
+       if (server_lpr != cmdline_lpr)
+               lls_free_parse_result(cmdline_lpr, CMD_PTR);
+}
+
 /**
  * The main function of para_server.
  *
@@ -564,17 +679,38 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds,
 int main(int argc, char *argv[])
 {
        int ret;
+       struct server_command_task server_command_task_struct,
+               *sct = &server_command_task_struct;
 
        sched.default_timeout.tv_sec = 1;
        sched.select_function = server_select;
 
-       server_init(argc, argv);
+       server_init(argc, argv, sct);
        mutex_lock(mmd_mutex);
        ret = schedule(&sched);
+       /*
+        * We hold the mmd lock: it was re-acquired in server_select()
+        * after the select call.
+        */
+       mutex_unlock(mmd_mutex);
        sched_shutdown(&sched);
-       if (ret < 0) {
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-               exit(EXIT_FAILURE);
+       crypt_shutdown();
+       signal_shutdown(signal_task);
+       if (!process_is_command_handler()) { /* parent (server) */
+               mutex_destroy(mmd_mutex);
+               daemon_set_hooks(NULL, NULL); /* only one process remaining */
+               mutex_destroy(log_mutex);
+               deplete_close_on_fork_list();
+               if (ret < 0)
+                       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       } else {
+               alarm(ALARM_TIMEOUT);
+               close_listed_fds();
+               ret = handle_connect(sct->child_fd);
        }
-       exit(EXIT_SUCCESS);
+       vss_shutdown();
+       shm_detach(mmd);
+       user_list_deplete();
+       free_lpr();
+       exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
 }
diff --git a/server.cmd b/server.cmd
deleted file mode 100644 (file)
index 5f46ba1..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-BN: server
-SF: command.c
-SN: list of server commands
----
-N: ff
-P: VSS_READ | VSS_WRITE
-D: Jump N seconds forward or backward.
-U: ff n[-]
-H: This sets the 'R' (reposition request) bit of the vss status flags
-H: which enqueues a request to jump n seconds forwards or backwards.
-H:
-H: Example:
-H:
-H:     para_client ff 30-
-H:
-H: jumps 30 seconds backwards.
----
-N: help
-P: 0
-D: Print online help.
-U: help [command]
-H: Without any arguments, help prints a list of available commands. When
-H: called with a command name as first argument, it prints the description
-H: of that command.
----
-N: hup
-P: VSS_WRITE
-D: Reload config file, log file and user list.
-U: hup
-H: Reread the config file and the user list file, close and reopen the log
-H: file, and ask the afs process to do the same. Sending the HUP signal to
-H: the server process has the same effect.
----
-N: jmp
-P: VSS_READ | VSS_WRITE
-D: Jump to the given position.
-U: jmp n
-H: Set the 'R' (reposition request) bit of the vss status flags and enqueue a
-H: request to jump to n% of the current audio file, where 0 <= n <= 100.
----
-N: next
-P: VSS_READ | VSS_WRITE
-D: Close the current audio file.
-U: next
-H: Set the 'N' (next audio file) bit of the vss status flags which instructs the
-H: server to close its current audio file if necessary. If the 'P' bit (playing)
-H: is on, playing continues with the next audio file. This command is equivalent
-H: to stop if paused, and has no effect if stopped.
----
-N: nomore
-P: VSS_READ | VSS_WRITE
-D: Stop playing after current audio file.
-U: nomore
-H: Set the 'O' (no more) bit of the vss status flags which asks para_server to
-H: clear the 'P' (playing) bit after the 'N' (next audio file) bit transitions
-H: from off to on (because the end of the current audio file is reached). Use this
-H: command instead of stop if you don't like sudden endings.
----
-N: pause
-P: VSS_READ | VSS_WRITE
-D: Pause current audio file.
-U: pause
-H: Clear the 'P' (playing) bit of the vss status flags.
----
-N: play
-P: VSS_READ | VSS_WRITE
-D: Start or resume playing.
-U: play
-H: Set the 'P' (playing) bit of the vss status flags.
----
-N: sender
-P: VSS_READ | VSS_WRITE
-D: Control paraslash senders.
-U: sender [s cmd [arguments]]
-H: Send a command to a specific sender. The following commands are available, but
-H: not all senders support every command.
-H:
-H:     help, on, off, add, delete, allow, deny, status.
-H:
-H: The help command prints the help text of the given sender. If no command is
-H: given the list of compiled in senders is shown.
-H:
-H: Example:
-H:
-H:     para_client sender http help
----
-N: si
-P: 0
-D: Print server info.
-U: si
-H: Show server and afs PID, number of connections, uptime and more.
----
-N: stat
-P: VSS_READ
-D: Print information about the current audio file.
-U: stat [-n=num] [-p]
-H: If -n is given, exit after the status information has been shown n times.
-H: Otherwise, the command runs in an endless loop.
-H:
-H: The -p option activates parser-friendly output: Each status item is
-H: prefixed with its size in bytes and the status item identifiers are
-H: printed as numerical values.
----
-N: stop
-P: VSS_READ | VSS_WRITE
-D: Stop playing.
-U: stop
-H: Clear the 'P' (playing) bit and set the 'N' (next audio file) bit of the vss
-H: status flags, effectively stopping playback.
----
-N: tasks
-P: 0
-D: List server tasks.
-U: tasks
-H: For each task, print ID, status and name.
----
-N: term
-P: VSS_READ | VSS_WRITE
-D: Ask the server to terminate.
-U: term
-H: Shut down the server. Instead of this command, you can also send SIGINT or
-H: SIGTERM to the para_server process. It should never be necessary to send
-H: SIGKILL.
----
-N: version
-P: 0
-D: Print the git version string of para_server.
-U: version
-H: Show version and other info.
index 8de691ca321bd49db2d7c25398537f31b7d03a0c..da75d86bdf191b130d02da12f49172ac5e0482d7 100644 (file)
--- a/server.h
+++ b/server.h
@@ -1,18 +1,10 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file server.h Common server data structures. */
 
 /** Size of the selector_info and audio_file info strings of struct misc_meta_data. */
 #define MMD_INFO_SIZE 16384
 
-/** The maximum length of the host component in an URL */
-#define MAX_HOSTLEN 256
-
-
 /** Arguments for the sender command. */
 struct sender_command_data {
        /** Greater than zero indicates that a sender cmd is already queued. */
@@ -48,8 +40,6 @@ struct sender_command_data {
  * propagate to the stat command handlers.
  */
 struct misc_meta_data {
-       /** The size of the current audio file in bytes. */
-       size_t size;
        /** The "old" status flags -- commands may only read them. */
        unsigned int vss_status_flags;
        /** The new status flags -- commands may set them. */
@@ -79,8 +69,6 @@ struct misc_meta_data {
        unsigned int num_connects;
        /** The number of connections currently active. */
        unsigned int active_connections;
-       /** The process id of the audio file selector. */
-       pid_t afs_pid;
        /** This gets updated by afs and contains its current mode. */
        char afs_mode_string[MAXLINE];
        /** Used by the sender command. */
@@ -89,9 +77,40 @@ struct misc_meta_data {
        struct audio_file_data afd;
 };
 
-/** Command line options for para_server. */
-extern struct server_args_info conf;
+extern pid_t afs_pid;
+extern struct lls_parse_result *server_lpr;
+
+/**
+ * Get a reference to the supercommand of para_server.
+ *
+ * This is needed for parsing the command line and for the ENUM_STRING_VAL()
+ * macro below. The latter macro is used in command.c, so CMD_PTR() can not
+ * be made local to server.c.
+ */
+#define CMD_PTR (lls_cmd(0, server_suite))
+
+/** Get the parse result of an option to para_server. */
+#define OPT_RESULT(_name) (lls_opt_result( \
+               LSG_SERVER_PARA_SERVER_OPT_ ## _name, server_lpr))
+
+/** How many times a server option was given. */
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+
+/** The (first) argument to a server option of type string. */
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+
+/** The (first) argument to a server option of type uint32. */
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
+
+/** The (first) argument to a server option of type int32. */
+#define OPT_INT32_VAL(_name) (lls_int32_val(0, OPT_RESULT(_name)))
+
+/** Get the string which corresponds to an enum constant. */
+#define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \
+       lls_opt(LSG_SERVER_PARA_SERVER_OPT_ ## _name, CMD_PTR)))
 
-__noreturn void handle_connect(int fd, const char *peername);
-void parse_config_or_die(int override);
+int handle_connect(int fd);
+void parse_config_or_die(bool reload);
 char *server_get_tasks(void);
+bool process_is_command_handler(void);
+void free_lpr(void);
index 9b64341a8ea8d883b1eac56a5c7db8cab4a23eae..ed7867a1c37027d7a49d1047bd349c5976d0e8d7 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file sideband.c Implementation of the sideband API. */
 
index c9b698f978634858ae3ff8c95150975677be35ac..6973b845e01527d609bd0ce3b3eac9336e4398ba 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file sideband.h exported symbols from sideband.c */
 
@@ -101,7 +97,7 @@ struct sb_context;
 /**
  * The type of a sideband transformation.
  *
- * The sideband API allows to filter all data through an arbitrary
+ * The sideband API allows the filtering of data through an arbitrary
  * transformation, which is useful for crypto purposes. The transformation may
  * either transform the data in place, or return a pointer to a new buffer
  * which contains the transformed source buffer. The internal sideband
index 5d6e6e45dcd8e728c1bfcc6e08899bf08d4fc512..32d6ab6624e5f493ac393b630a603c0968f11497 100644 (file)
--- a/signal.c
+++ b/signal.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 /** \file signal.c Signal handling functions. */
 
 #include <signal.h>
@@ -25,7 +21,7 @@ static int signal_pipe[2];
  * This function creates a pipe, the signal pipe, to deliver pending
  * signals to the application (Bernstein's trick). It should be called
  * during the application's startup part, followed by subsequent calls
- * to para_install_sighandler() for each signal that should be caught.
+ * to \ref para_install_sighandler() for each signal that should be caught.
  *
  * A generic signal handler is used for all signals simultaneously. When a
  * signal arrives, the signal handler writes the number of the signal received
@@ -33,7 +29,7 @@ static int signal_pipe[2];
  * by checking if the file descriptor of the other end of the signal pipe is
  * ready for reading, see select(2).
  *
- * \return This function either succeeds or calls exit(2) to terminate the
+ * \return This function either succeeds or calls exit(3) to terminate the
  * current process. On success, a signal task structure is returned.
  */
 struct signal_task *signal_init_or_die(void)
index 742f677a2568cad3001ae9cc2220344ebf7f8b1b..e5532ded5a9378619f7951708b49c5b38bf52d18 100644 (file)
--- a/signal.h
+++ b/signal.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file signal.h exported symbols from signal.c */
 
diff --git a/spx.h b/spx.h
index 1567ce8789ccca9598fee6036b23d37e2549fec9..4f46f03b73a62f357938af5285b274476db46aca 100644 (file)
--- a/spx.h
+++ b/spx.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /**
  *  \file spx.h Structures and prototypes common to the speex audio format
index 4e318af19af8940570887ac3ebf8edb7a4c25b85..caeacb1921341b04b26f117d5de6607a80ab95e9 100644 (file)
--- a/spx_afh.c
+++ b/spx_afh.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /* This file is based on speexdec.c, by Jean-Marc Valin, see below. */
 
@@ -152,13 +148,13 @@ static int spx_get_file_info(char *map, size_t numbytes, __a_unused int fd,
                struct afh_info *afhi)
 {
        struct private_spx_data psd;
-       struct ogg_afh_callback_info spx_callback_info = {
+       struct oac_callback_info spx_callback_info = {
                .packet_callback = spx_packet_callback,
                .private_data = &psd,
        };
 
        memset(&psd, 0, sizeof(psd));
-       return ogg_get_file_info(map, numbytes, afhi, &spx_callback_info);
+       return oac_get_file_info(map, numbytes, afhi, &spx_callback_info);
 }
 
 static size_t spx_make_meta_packet(struct taginfo *tags, char **result)
@@ -244,7 +240,7 @@ static int spx_rewrite_tags(const char *map, size_t mapsize,
        int ret;
 
        meta_sz = spx_make_meta_packet(tags, &meta_packet);
-       ret = ogg_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz);
+       ret = oac_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz);
        free(meta_packet);
        return ret;
 }
@@ -252,13 +248,14 @@ static int spx_rewrite_tags(const char *map, size_t mapsize,
 static const char * const speex_suffixes[] = {"spx", "speex", NULL};
 
 /**
- * The init function of the ogg/speex audio format handler.
+ * The ogg/speex audio format handler.
  *
- * \param afh Pointer to the struct to initialize.
+ * This codec is considered obsolete because the opus codec surpasses its
+ * performance in all areas. It is only compiled in if both the ogg and the
+ * speex library are installed.
  */
-void spx_afh_init(struct audio_format_handler *afh)
-{
-       afh->get_file_info = spx_get_file_info,
-       afh->suffixes = speex_suffixes;
-       afh->rewrite_tags = spx_rewrite_tags;
-}
+const struct audio_format_handler spx_afh = {
+       .get_file_info = spx_get_file_info,
+       .suffixes = speex_suffixes,
+       .rewrite_tags = spx_rewrite_tags,
+};
index c850cd198683b103215ee5a01615d27248937caa..b8eb99122dc5e273154302752f2be47acd9d0f99 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (C) 2002-2006 Jean-Marc Valin
  * Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>
  *
- * Licensed under the GPL v2. For licencing details see COPYING.
+ * Licensed under the GPL v2, see file COPYING.
  */
 
 /**
index 644d287aaf6a4daba32dd023c9770691aa3f4d24..7be817ddaa49bf97e5d4025c43be63b42da9cd4c 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (C) 2002-2006 Jean-Marc Valin
  * Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>
  *
- * Licensed under the GPL v2. For licencing details see COPYING.
+ * Licensed under the GPL v2, see file COPYING.
  */
 
 /** \file spxdec_filter.c Paraslash's ogg/speex decoder. */
@@ -48,9 +48,9 @@
 #include <speex/speex_callbacks.h>
 
 #include "para.h"
+#include "portable_io.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "error.h"
@@ -122,12 +122,6 @@ static int speexdec_init(struct filter_node *fn)
        return 1;
 }
 
-#if !defined(__LITTLE_ENDIAN__) && ( defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) )
-#define le_short(s) ((short) ((unsigned short) (s) << 8) | ((unsigned short) (s) >> 8))
-#else
-#define le_short(s) ((short) (s))
-#endif
-
 /**
  * Size of the output buffer.
  *
@@ -179,7 +173,7 @@ static int speexdec_write_frames(int packet_no,
                samples = new_frame_size * psd->shi.channels;
                btr_output = para_malloc(2 * samples);
                for (i = 0; i < samples; i++)
-                       btr_output[i] = le_short(output[i + skip_idx]);
+                       btr_output[i] = read_u16(output + i + skip_idx);
                btr_add_output((char *)btr_output, samples * 2, btrn);
        }
        return 1;
@@ -308,16 +302,10 @@ fail:
        return ret;
 }
 
-/**
- * The init function of the ogg/speex decoder.
- *
- * \param f Its fields are filled in by the function.
- */
-void spxdec_filter_init(struct filter *f)
-{
-       f->open = spxdec_open;
-       f->close = speexdec_close;
-       f->pre_select = generic_filter_pre_select;
-       f->post_select = speexdec_post_select;
-       f->execute = speexdec_execute;
-}
+const struct filter lsg_filter_cmd_com_spxdec_user_data = {
+       .open = spxdec_open,
+       .close = speexdec_close,
+       .pre_select = generic_filter_pre_select,
+       .post_select = speexdec_post_select,
+       .execute = speexdec_execute,
+};
diff --git a/stat.c b/stat.c
index ac18363df2899156d7ed8b452b8d6143fcb2c40d..43e7998ad3fababb6ed74e18b40ad34f99b38244 100644 (file)
--- a/stat.c
+++ b/stat.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /**
  *  \file stat.c Functions used for sending/receiving the status of para_server
diff --git a/stdin.c b/stdin.c
index 7b70690b9a5f80626ad979595ee211bcf0037ded..9408235a045a6767e2acf443208d95196990d379 100644 (file)
--- a/stdin.c
+++ b/stdin.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file stdin.c Functions that deal with reading from stdin. */
 
diff --git a/stdin.h b/stdin.h
index ca33b08a1affae80e07eef5be59d78b4f35006a4..8caee4cb316e876e81c085474c81bcaa6bba1805 100644 (file)
--- a/stdin.h
+++ b/stdin.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file stdin.h The standard in task structure. */
 
index b26661594a940373fe5e6bd20e67c54607e70b4c..1f7791094a33a961c193c273a4c09e9b16f2be07 100644 (file)
--- a/stdout.c
+++ b/stdout.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file stdout.c Functions that deal with writing to stdout. */
 
@@ -89,7 +85,7 @@ void stdout_task_register(struct stdout_task *sot, struct sched *s)
                .name = "stdout",
        };
 
-       /* See stdin.c for details. */
+       /* See \ref stdin.c for details. */
        ret = fcntl(STDOUT_FILENO, F_GETFL);
        if (ret < 0) {
                PARA_EMERG_LOG("F_GETFL: %s\n", strerror(errno));
index 0559fb5c1773c2ea9a55b34b1135725a2415d3d1..114c44f492add74d7e42e7bfdd2c0dde02f92698 100644 (file)
--- a/stdout.h
+++ b/stdout.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file stdout.h Writing to stdout via buffer trees. */
 
index 6033a008dbf154953de673f85048eb78812d1722..198e9f1d286ee2228638a2a7994fe9e65f41ccac 100644 (file)
--- a/string.c
+++ b/string.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2004 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file string.c Memory allocation and string handling functions. */
 
@@ -10,7 +6,6 @@
 
 #include <pwd.h>
 #include <sys/utsname.h> /* uname() */
-#include <string.h>
 #include <regex.h>
 #include <langinfo.h>
 #include <wchar.h>
@@ -118,9 +113,9 @@ __must_check __malloc char *para_strdup(const char *s)
 }
 
 /**
- * Print a formated message to a dynamically allocated string.
+ * Print a formatted message to a dynamically allocated string.
  *
- * \param result The formated string is returned here.
+ * \param result The formatted string is returned here.
  * \param fmt The format string.
  * \param ap Initialized list of arguments.
  *
@@ -190,7 +185,7 @@ __printf_2_3 unsigned xasprintf(char **result, const char *fmt, ...)
  * \return This function either returns a pointer to a string that must be
  * freed by the caller or aborts without returning.
  *
- * \sa printf(3), xasprintf().
+ * \sa printf(3), \ref xasprintf().
  */
 __must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...)
 {
@@ -204,17 +199,22 @@ __must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...)
 }
 
 /**
- * Free the content of a pointer and set it to \p NULL.
+ * Free the content of a pointer and set it to NULL.
  *
- * This is equivalent to "free(*arg); *arg = NULL;".
+ * \param arg A pointer to the pointer whose content should be freed.
  *
- * \param arg The pointer whose content should be freed.
+ * If arg is NULL, the function returns immediately. Otherwise it frees the
+ * memory pointed to by *arg and sets *arg to NULL. Hence callers have to pass
+ * the *address* of the pointer variable that points to the memory which should
+ * be freed.
  */
 void freep(void *arg)
 {
-       void **ptr = (void **)arg;
-       free(*ptr);
-       *ptr = NULL;
+       if (arg) {
+               void **ptr = arg;
+               free(*ptr);
+               *ptr = NULL;
+       }
 }
 
 /**
@@ -230,7 +230,7 @@ void freep(void *arg)
  * return \a a without making a copy of \a a.  Otherwise, construct the
  * concatenation \a c, free \a a (but not \a b) and return \a c.
  *
- * \sa strcat(3)
+ * \sa strcat(3).
  */
 __must_check __malloc char *para_strcat(char *a, const char *b)
 {
@@ -548,7 +548,7 @@ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...)
  *
  * \return Standard.
  *
- * \sa para_atoi32(), strtol(3), atoi(3).
+ * \sa \ref para_atoi32(), strtol(3), atoi(3).
  */
 int para_atoi64(const char *str, int64_t *value)
 {
@@ -585,7 +585,7 @@ int para_atoi64(const char *str, int64_t *value)
  *
  * \return Standard.
  *
- * \sa para_atoi64().
+ * \sa \ref para_atoi64().
 */
 int para_atoi32(const char *str, int32_t *value)
 {
index 52f989417d8071a1f2ed33d99ce7fd8ba18d66c1..10379a0e83098f392e8653467b826925376eda15 100644 (file)
--- a/string.h
+++ b/string.h
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file string.h exported symbols from string.c */
 
@@ -20,7 +16,7 @@ struct para_buffer {
        size_t size;
        /** The maximal size this buffer may grow. Zero means unlimited. */
        size_t max_size;
-       /** \sa para_buffer_flags. */
+       /** \sa \ref para_buffer_flags. */
        unsigned flags;
        /** The next para_printf() will write at this offset. */
        size_t offset;
@@ -37,7 +33,7 @@ struct para_buffer {
 /**
  * Controls the behavior of for_each_line().
  *
- * \sa for_each_line().
+ * \sa \ref for_each_line().
  */
 enum for_each_line_flags {
        /** Activate read-only mode. */
@@ -52,14 +48,14 @@ int for_each_line(unsigned flags, char *buf, size_t size,
                line_handler_t *line_handler, void *private_data);
 
 /**
 * Write the contents of a status item to a para_buffer.
 *
 * \param b The para_buffer.
 * \param n The number of the status item.
 * \param f A format string.
 *
 * \return The return value of the underlying call to para_printf().
 */
+ * Write the contents of a status item to a para_buffer.
+ *
+ * \param b The para_buffer.
+ * \param n The number of the status item.
+ * \param f A format string.
+ *
* \return The return value of the underlying call to \ref para_printf().
+ */
 #define WRITE_STATUS_ITEM(b, n, f, ...) (\
 { \
        if ((b)->flags & PBF_SIZE_PREFIX) { \
@@ -71,7 +67,7 @@ int for_each_line(unsigned flags, char *buf, size_t size,
 } \
 )
 
-__must_check __malloc void *para_realloc(void *p, size_t size);
+__must_check void *para_realloc(void *p, size_t size);
 __must_check __malloc void *para_malloc(size_t size);
 __must_check __malloc void *para_calloc(size_t size);
 __must_check __malloc char *para_strdup(const char *s);
index 82e86e90cf50c4bf77faac44183286c536caab26..f23545adb68342322fbeaef4c45c73c663a969be 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2013 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2013 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file sync_filter.c Playback synchronization filter. */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "filter_cmd.lsg.h"
 #include "para.h"
-#include "sync_filter.cmdline.h"
 #include "list.h"
 #include "net.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
@@ -42,7 +38,7 @@ struct sync_buddy {
        struct list_head node;
 };
 
-/* Allocated in ->open() */
+/* Allocated in ->open(), stored in fn->private_data */
 struct sync_filter_context {
        int listen_fd;
        struct list_head buddies;
@@ -50,12 +46,6 @@ struct sync_filter_context {
        bool ping_sent;
 };
 
-/* Allocated and freed in ->parse_config() and ->free_config(). */
-struct sync_filter_config {
-       struct sync_filter_args_info *conf;
-       struct sync_buddy_info *buddy_info;
-};
-
 #define FOR_EACH_BUDDY(_buddy, _list) \
        list_for_each_entry(_buddy, _list, node)
 #define FOR_EACH_BUDDY_SAFE(_buddy, _tmp_buddy, _list) \
@@ -90,59 +80,59 @@ static void sync_close(struct filter_node *fn)
        fn->private_data = NULL;
 }
 
-static void sync_free_config(void *conf)
+static void sync_teardown(const struct lls_parse_result *lpr, void *conf)
 {
-       struct sync_filter_config *sfc = conf;
-       int i;
+       struct sync_buddy_info *sbi = conf;
+       int i, num_buddies = FILTER_CMD_OPT_GIVEN(SYNC, BUDDY, lpr);
 
-       for (i = 0; i < sfc->conf->buddy_given; i++) {
-               free(sfc->buddy_info[i].host);
-               freeaddrinfo(sfc->buddy_info[i].ai);
+       for (i = 0; i < num_buddies; i++) {
+               free(sbi[i].host);
+               freeaddrinfo(sbi[i].ai);
        }
-       sync_filter_cmdline_parser_free(sfc->conf);
-       free(sfc);
+       free(sbi);
 }
 
 static void sync_open(struct filter_node *fn)
 {
        int i, ret;
-       struct sync_filter_config *sfc = fn->conf;
        struct sync_buddy *buddy;
        struct sync_filter_context *ctx;
-
-       assert(sfc);
+       struct sync_buddy_info *sbi = fn->conf;
+       uint32_t port = FILTER_CMD_OPT_UINT32_VAL(SYNC, PORT, fn->lpr);
+       unsigned buddy_given;
+       const struct lls_opt_result *r_b;
 
        ctx = fn->private_data = para_calloc(sizeof(*ctx));
        INIT_LIST_HEAD(&ctx->buddies);
-       ctx->listen_fd = -1;
 
        /* create socket to listen for incoming packets */
        ret = makesock(
                IPPROTO_UDP,
                true /* passive */,
                NULL /* no host required */,
-               sfc->conf->port_arg,
+               port,
                NULL /* no flowopts */
        );
        if (ret < 0) {
-               PARA_ERROR_LOG("could not create UDP listening socket %d\n",
-                       sfc->conf->port_arg);
+               PARA_ERROR_LOG("could not create UDP listening socket %u\n",
+                       port);
                return;
        }
        ctx->listen_fd = ret;
        PARA_INFO_LOG("listening on fd %d\n", ctx->listen_fd);
 
-       for (i = 0; i < sfc->conf->buddy_given; i++) {
-               struct sync_buddy_info *sbi = sfc->buddy_info + i;
-               const char *url = sfc->conf->buddy_arg[i];
+       r_b = FILTER_CMD_OPT_RESULT(SYNC, BUDDY, fn->lpr);
+       buddy_given = lls_opt_given(r_b);
+       for (i = 0; i < buddy_given; i++) {
                int fd;
+               const char *url = lls_string_val(i, r_b);
 
                /* make buddy udp socket from address info */
                assert(sbi->ai);
                ret = makesock_addrinfo(
                        IPPROTO_UDP,
                        false /* not passive */,
-                       sbi->ai,
+                       sbi[i].ai,
                        NULL /* no flowopts */
                );
                if (ret < 0) {
@@ -160,7 +150,7 @@ static void sync_open(struct filter_node *fn)
                }
                buddy = para_malloc(sizeof(*buddy));
                buddy->fd = fd;
-               buddy->sbi = sbi;
+               buddy->sbi = sbi + i;
                buddy->ping_received = false;
                para_list_add(&buddy->node, &ctx->buddies);
 
@@ -172,61 +162,50 @@ fail:
 }
 
 /*
- * At parse config time, we build an array of struct sync_buddy_info with one
- * entry for each buddy given in the arguments. This array is not affected by
- * sync_close(), so information stored there can be used for multiple instances
- * (para_audiod). We store the resolved url and the ->disabled bit in this
- * array.
+ * Build an array of struct sync_buddy_info with one entry for each buddy given
+ * in the arguments. This array is not affected by sync_close(), so information
+ * stored there can be used for multiple instances (para_audiod). We store the
+ * resolved url and the ->disabled bit in this array.
  */
-static int sync_parse_config(int argc, char **argv, void **result)
+static void *sync_setup(const struct lls_parse_result *lpr)
 {
-       int i, ret, n;
-       struct sync_filter_config *sfc;
-       struct sync_filter_args_info *conf = para_malloc(sizeof(*conf));
-
-       sync_filter_cmdline_parser(argc, argv, conf); /* exits on error */
-       sfc = para_calloc(sizeof(*sfc));
-       sfc->conf = conf;
-       n = conf->buddy_given;
-       sfc->buddy_info = para_malloc((n + 1) * sizeof(*sfc->buddy_info));
-       PARA_INFO_LOG("initializing buddy info array of length %d\n", n);
+       int i, ret;
+       unsigned n;
+       struct sync_buddy_info *sbi;
+       const struct lls_opt_result *r_b;
+
+       r_b = FILTER_CMD_OPT_RESULT(SYNC, BUDDY, lpr);
+       n = lls_opt_given(r_b);
+       sbi = para_malloc(n * sizeof(*sbi));
+       PARA_INFO_LOG("initializing buddy info array of length %u\n", n);
        for (i = 0; i < n; i++) {
-               const char *url = conf->buddy_arg[i];
+               const char *url = lls_string_val(i, r_b);
                size_t len = strlen(url);
                char *host = para_malloc(len + 1);
                int port;
                struct addrinfo *ai;
-               struct sync_buddy_info *sbi = sfc->buddy_info + i;
 
                if (!parse_url(url, host, len, &port)) {
-                       free(host);
                        PARA_ERROR_LOG("could not parse url %s\n", url);
-                       ret = -ERRNO_TO_PARA_ERROR(EINVAL);
-                       goto fail;
+                       exit(EXIT_FAILURE);
                }
                if (port < 0)
-                       port = conf->port_arg;
+                       port = FILTER_CMD_OPT_UINT32_VAL(SYNC, PORT, lpr);
                ret = lookup_address(IPPROTO_UDP, false /* not passive */,
                        host, port, &ai);
                if (ret < 0) {
-                       PARA_ERROR_LOG("host lookup failure for %s\n", url);
-                       free(host);
-                       goto fail;
+                       PARA_ERROR_LOG("host lookup failure for %s: %s\n",
+                               url, para_strerror(-ret));
+                       exit(EXIT_FAILURE);
                }
-               sbi->url = url;
-               sbi->host = host;
-               sbi->port = port;
-               sbi->ai = ai;
-               sbi->disabled = false;
+               sbi[i].url = url;
+               sbi[i].host = host;
+               sbi[i].port = port;
+               sbi[i].ai = ai;
+               sbi[i].disabled = false;
                PARA_DEBUG_LOG("buddy #%d: %s\n", i, url);
        }
-       *result = sfc;
-       return 1;
-fail:
-       assert(ret < 0);
-       PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-       sync_free_config(sfc);
-       return ret;
+       return sbi;
 }
 
 /*
@@ -260,11 +239,12 @@ static void sync_disable_active_buddies(struct sync_filter_context *ctx)
 }
 
 static void sync_set_timeout(struct sync_filter_context *ctx,
-               struct sync_filter_config *sfc)
+               struct lls_parse_result *lpr)
 {
+       uint32_t ms = FILTER_CMD_OPT_UINT32_VAL(SYNC, TIMEOUT, lpr);
        struct timeval to;
 
-       ms2tv(sfc->conf->timeout_arg, &to);
+       ms2tv(ms, &to);
        tv_add(now, &to, &ctx->timeout);
 }
 
@@ -273,7 +253,6 @@ static void sync_pre_select(struct sched *s, void *context)
        int ret;
        struct filter_node *fn = context;
        struct sync_filter_context *ctx = fn->private_data;
-       struct sync_filter_config *sfc = fn->conf;
 
        if (list_empty(&ctx->buddies))
                return sched_min_delay(s);
@@ -286,7 +265,7 @@ static void sync_pre_select(struct sched *s, void *context)
        if (ret == 0)
                return;
        if (ctx->timeout.tv_sec == 0) { /* must ping buddies */
-               sync_set_timeout(ctx, sfc);
+               sync_set_timeout(ctx, fn->lpr);
                return sched_min_delay(s);
        }
        if (sync_complete(ctx)) /* push down what we have */
@@ -310,7 +289,6 @@ static int sync_post_select(__a_unused struct sched *s, void *context)
        int ret;
        struct filter_node *fn = context;
        struct sync_filter_context *ctx = fn->private_data;
-       struct sync_filter_config *sfc = fn->conf;
        struct sync_buddy *buddy, *tmp;
 
        if (list_empty(&ctx->buddies))
@@ -324,7 +302,7 @@ static int sync_post_select(__a_unused struct sched *s, void *context)
        if (ret == 0)
                return 0;
        if (ctx->timeout.tv_sec == 0)
-               sync_set_timeout(ctx, sfc);
+               sync_set_timeout(ctx, fn->lpr);
        else {
                if (tv_diff(&ctx->timeout, now, NULL) < 0) {
                        sync_disable_active_buddies(ctx);
@@ -387,7 +365,8 @@ success:
        ret = -E_SYNC_COMPLETE; /* success */
        goto out;
 fail:
-       PARA_WARNING_LOG("%s\n", para_strerror(-ret));
+       if (ret != -E_BTR_EOF)
+               PARA_WARNING_LOG("%s\n", para_strerror(-ret));
 out:
        sync_close_buddies(ctx);
        btr_splice_out_node(&fn->btrn);
@@ -395,21 +374,11 @@ out:
        return ret;
 }
 
-/**
- * The synchronization filter.
- *
- * \param f Pointer to the struct to initialize.
- */
-void sync_filter_init(struct filter *f)
-{
-       struct sync_filter_args_info dummy;
-
-       sync_filter_cmdline_parser_init(&dummy);
-       f->open = sync_open;
-       f->close = sync_close;
-       f->pre_select = sync_pre_select;
-       f->post_select = sync_post_select;
-       f->parse_config = sync_parse_config;
-       f->free_config = sync_free_config;
-       f->help = (struct ggo_help)DEFINE_GGO_HELP(sync_filter);
-}
+const struct filter lsg_filter_cmd_com_sync_user_data = {
+       .setup = sync_setup,
+       .open = sync_open,
+       .pre_select = sync_pre_select,
+       .post_select = sync_post_select,
+       .close = sync_close,
+       .teardown = sync_teardown
+};
index 15bb6859b1c2e668245bf0663bc57d7593710f83..904c779353bbd5e08dfab8e449e607dead8a622c 100644 (file)
@@ -18,16 +18,18 @@ endif
 
 tests := $(sort $(wildcard $(test_dir)/t[0-9][0-9][0-9][0-9]-*.sh))
 
+check: $(tests)
 test: $(tests)
 
 $(tests): all
-       $(Q) $@ $(test_options)
+       $(call SAY, $(@))
+       $@ $(test_options)
 
 test-help:
-       $(Q) for t in $(tests); do $$t $(test_options) -h; done
+       @for t in $(tests); do $$t $(test_options) -h; done
 
 test-clean:
        $(RM) -r $(results_dir)
        $(RM) -r $(trash_dir)
 
-.PHONY: $(tests) test-help
+.PHONY: $(tests) test-help test-clean test check
index 554dfdf85dd654ef81c3114a847102ede377e6bc..0b30539acbbfd527a46c586e47818ac5f49d02fd 100755 (executable)
@@ -11,7 +11,7 @@ implementation.'
 test_require_objects "oggdec_filter"
 missing_objects="$result"
 
-test_require_executables oggdec sha1sum
+test_require_executables oggdec shasum
 missing_executables="$result"
 
 get_audio_file_paths ogg
@@ -28,9 +28,9 @@ for ogg in $oggs; do
                continue
        fi
        test_expect_success "${ogg##*/}" "
-               $PARA_FILTER -f oggdec < $ogg | sha1sum > filter.sha1 &&
-               oggdec --quiet --raw --output - -  < $ogg | sha1sum > oggdec.sha1 &&
-               diff -u filter.sha1 oggdec.sha1
+               $PARA_FILTER -f oggdec < $ogg | shasum > filter.sha &&
+               oggdec --quiet --raw --output - -  < $ogg | shasum > oggdec.sha &&
+               diff -u filter.sha oggdec.sha
        "
 done
 test_done
index 1963748f81547ac5706b992597cc41955abffa6d..03957464e17e2a50596149e59627cd7584424fe9 100755 (executable)
@@ -27,8 +27,8 @@ declare -a oggs_base=(${oggs[@]##*/})
 declare -a commands=() cmdline=() required_objects=() good=() bad=()
 i=0
 commands[$i]="help"
-cmdline[$i]="help"
-good[$i]='help server  ----'
+cmdline[$i]="help -l"
+good[$i]='help \{1,\}----'
 
 let i++
 commands[$i]="init"
@@ -69,7 +69,7 @@ bad[$i]='.'
 let i++
 commands[$i]="ls"
 required_objects[$i]='ogg_afh'
-cmdline[$i]="ls -l=v -F ${oggs[@]}"
+cmdline[$i]="ls -l=v ${oggs[@]}"
 good[$i]='^attributes_txt: 33'
 
 let i++
@@ -83,7 +83,7 @@ test_require_executables "ssh-keygen"
 missing_executables="$result"
 
 if [[ -z "$missing_objects" && -z "$missing_executables" ]]; then
-       ssh-keygen -q -t rsa -b 2048 -N "" -f $privkey
+       ssh-keygen -q -t rsa -b 2048 -N "" -m PEM -f $privkey
        key_gen_result=$?
 
        read &>/dev/null < /dev/tcp/localhost/$port
index fa32d4112e0ee80592b04c40214ffc7a868c9d89..ee1949c05a04d4fd4b81f2b9c847a403a753e2f5 100755 (executable)
@@ -13,8 +13,6 @@ filter/receiver/writer options as appropriate '
 
 . ${0%/*}/test-lib.sh
 
-rfw_regex='Options for .\{100,\}Options for ' # recv/filter/writer
-
 grep_man()
 {
        local regex="$1" exe="$2"
@@ -24,20 +22,19 @@ grep_man()
 # check that options of all reveivers/filters/writers are contained
 # in the man pages
 
-regex="$rfw_regex"
-test_expect_success 'para_recv: receiver options' "grep_man '$regex' recv"
-test_expect_success 'para_filter: filter options' "grep_man '$regex' filter"
-test_expect_success 'para_write: writer options' "grep_man '$regex' write"
+test_expect_success 'para_recv: receiver options' "grep_man 'RECEIVERS' recv"
+test_expect_success 'para_filter: filter options' "grep_man 'FILTERS' filter"
+test_expect_success 'para_write: writer options' "grep_man 'WRITERS' write"
 test_require_objects "audiod"
 if [[ -n "$result" ]]; then
        test_skip 'para_audiod' "missing object(s): $result"
 else
        test_expect_success 'para_audiod: receivers' \
-               "grep_man 'Options for the http receiver' audiod"
+               "grep_man 'RECEIVERS' audiod"
        test_expect_success 'para_audiod: filters' \
-               "grep_man 'Options for the compress filter' audiod"
+               "grep_man 'FILTERS' audiod"
        test_expect_success 'para_audiod: writers' \
-               "grep_man 'Options for the file writer' audiod"
+               "grep_man 'WRITERS' audiod"
 fi
 
 # check various command lists
@@ -56,7 +53,7 @@ missing_objects="$result"
 if [[ -n "$missing_objects" ]]; then
        test_skip "para_server" "missing object(s): $missing_objects"
 else
-       regex='LIST OF SERVER COMMANDS.\{100,\}LIST OF AFS COMMANDS'
+       regex='LIST OF SERVER COMMANDS.\{100,\}'
        test_expect_success 'para_server: server/afs commands' \
                "grep_man '$regex' server"
 fi
index 1f9913e3cf9ba49b4cb76cc5b985d914cb527b69..75249fe32b0b0e159b4b6ad1773efa2673b2cd22 100644 (file)
@@ -1,9 +1,8 @@
 #!/bin/bash
 
-# paraslash test suite helper functions
-# Licensed under the GPL v2. For licencing details see COPYING.
-# uses ideas and code from git's test-lib.sh, Copyright (c) 2005 Junio C Hamano
-
+# Test suite helper functions, uses ideas and code from git's test-lib.sh,
+# Copyright (c) 2005 Junio C Hamano. Licensed under the GPL v2, see file
+# COPYING.
 
 get_audio_file_paths()
 {
@@ -311,7 +310,7 @@ fi
 fixup_dirs
 
 [[ -z "$o_executables" ]] && o_executables="para_afh para_audioc para_audiod
-       para_client para_fade para_filter para_gui para_recv para_server
+       para_client para_mixer para_filter para_gui para_recv para_server
        para_write"
 for exe in $o_executables; do
        export $(tr 'a-z' 'A-Z' <<< $exe)="$o_executables_dir/$exe"
diff --git a/time.c b/time.c
index 6bb9a50f5b32f7441943817dc22f868d0f0131cd..bb47b31b7b262ca8a988377d899e166abdd5d029 100644 (file)
--- a/time.c
+++ b/time.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 #include "para.h"
 /** \file time.c Helper functions for dealing with time values. */
@@ -198,22 +194,14 @@ void compute_chunk_time(long unsigned chunk_num,
 struct timeval *clock_get_realtime(struct timeval *tv)
 {
        static struct timeval user_friendly;
+       struct timespec t;
+       int ret;
 
        if (!tv)
                tv = &user_friendly;
-#ifdef HAVE_CLOCK_GETTIME
-       {
-               struct timespec t;
-               int ret;
-
-               ret = clock_gettime(CLOCK_REALTIME, &t);
-               assert(ret == 0);
-               tv->tv_sec = t.tv_sec;
-               tv->tv_usec = t.tv_nsec / 1000;
-       }
-#else
-       #include <sys/time.h>
-       gettimeofday(tv, NULL);
-#endif /* HAVE_CLOCK_GETTIME */
+       ret = clock_gettime(CLOCK_REALTIME, &t);
+       assert(ret == 0);
+       tv->tv_sec = t.tv_sec;
+       tv->tv_usec = t.tv_nsec / 1000;
        return tv;
 }
index b803b4976b52d0e89fdae2c3ee7edbdb8a0a311b..58d45ab43d6508619d34186b3f4a23f6d455ecc7 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 /** \file udp_recv.c Paraslash's udp receiver */
 
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "recv_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "portable_io.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "recv.h"
-#include "udp_recv.cmdline.h"
 #include "string.h"
 #include "net.h"
 #include "fd.h"
@@ -103,13 +99,6 @@ static void udp_recv_close(struct receiver_node *rn)
        btr_pool_free(rn->btrp);
 }
 
-static void *udp_recv_parse_config(int argc, char **argv)
-{
-       struct udp_recv_args_info *tmp = para_calloc(sizeof(*tmp));
-       udp_recv_cmdline_parser(argc, argv, tmp);
-       return tmp;
-}
-
 /*
  * Perform AF-independent joining of multicast receive addresses.
  *
@@ -173,58 +162,33 @@ err:
 
 static int udp_recv_open(struct receiver_node *rn)
 {
-       struct udp_recv_args_info *c = rn->conf;
-       char  *iface = c->iface_given ? c->iface_arg : NULL;
+       struct lls_parse_result *lpr = rn->lpr;
+       const char *iface = RECV_CMD_OPT_STRING_VAL(UDP, IFACE, lpr);
+       const char *host = RECV_CMD_OPT_STRING_VAL(UDP, HOST, lpr);
+       uint32_t port = RECV_CMD_OPT_UINT32_VAL(UDP, PORT, lpr);
        int ret;
 
-       ret = makesock(IPPROTO_UDP, 1, c->host_arg, c->port_arg, NULL);
+       ret = makesock(IPPROTO_UDP, 1, host, port, NULL);
        if (ret < 0)
-               goto err;
+               return ret;
        rn->fd = ret;
-
        ret = mcast_receiver_setup(rn->fd, iface);
-       if (ret < 0) {
-               close(rn->fd);
+       if (ret < 0)
                goto err;
-       }
-
        ret = mark_fd_nonblocking(rn->fd);
-       if (ret < 0) {
-               close(rn->fd);
+       if (ret < 0)
                goto err;
-       }
-       PARA_INFO_LOG("receiving from %s:%d, fd=%d\n", c->host_arg,
-               c->port_arg, rn->fd);
+       PARA_INFO_LOG("receiving from %s:%u, fd=%d\n", host, port, rn->fd);
        rn->btrp = btr_pool_new("udp_recv", 320 * 1024);
        return rn->fd;
 err:
+       close(rn->fd);
        return ret;
 }
 
-static void udp_recv_free_config(void *conf)
-{
-       udp_recv_cmdline_parser_free(conf);
-       free(conf);
-}
-
-/**
- * The init function of the udp receiver.
- *
- * \param r Pointer to the receiver struct to initialize.
- *
- * Initialize all function pointers of \a r.
- */
-void udp_recv_init(struct receiver *r)
-{
-       struct udp_recv_args_info dummy;
-
-       udp_recv_cmdline_parser_init(&dummy);
-       r->open = udp_recv_open;
-       r->close = udp_recv_close;
-       r->pre_select = udp_recv_pre_select;
-       r->post_select = udp_recv_post_select;
-       r->parse_config = udp_recv_parse_config;
-       r->free_config = udp_recv_free_config;
-       r->help = (struct ggo_help)DEFINE_GGO_HELP(udp_recv);
-       udp_recv_cmdline_parser_free(&dummy);
-}
+const struct receiver lsg_recv_cmd_com_udp_user_data = {
+       .open = udp_recv_open,
+       .close = udp_recv_close,
+       .pre_select = udp_recv_pre_select,
+       .post_select = udp_recv_post_select,
+};
index 2eb33b537606725dd71f1ddc4bb531814c659c7d..96a25d370a99d3fdcb6782ef9451ae86b1cfc3a4 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file udp_send.c Para_server's udp sender. */
 
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
-#include "server.cmdline.h"
+#include "server.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "string.h"
 #include "afh.h"
+#include "net.h"
 #include "server.h"
 #include "list.h"
 #include "send.h"
 #include "sched.h"
 #include "vss.h"
 #include "portable_io.h"
-#include "net.h"
 #include "fd.h"
 #include "close_on_fork.h"
 
@@ -59,6 +56,8 @@ static void udp_close_target(struct sender_client *sc)
        size_t len;
        struct udp_target *ut = sc->private_data;
 
+       if (process_is_command_handler())
+               return;
        if (ut->sent_fec_eof)
                return;
        PARA_NOTICE_LOG("sending FEC EOF\n");
@@ -75,8 +74,11 @@ static void udp_delete_target(struct sender_client *sc, const char *msg)
 
        PARA_NOTICE_LOG("deleting %s (%s) from list\n", sc->name, msg);
        udp_close_target(sc);
-       close(sc->fd);
-       del_close_on_fork_list(sc->fd);
+       /* command handlers already called close_listed_fds() */
+       if (!process_is_command_handler()) {
+               close(sc->fd);
+               del_close_on_fork_list(sc->fd);
+       }
        vss_del_fec_client(ut->fc);
        list_del(&sc->node);
        free(sc->name);
@@ -95,11 +97,11 @@ static int mcast_sender_setup(struct sender_client *sc)
 {
        struct sockaddr_storage ss;
        socklen_t sslen = sizeof(ss);
-       int ttl = conf.udp_ttl_arg, id = 0;
+       int ttl = OPT_INT32_VAL(UDP_TTL), id = 0;
        const int on = 1;
 
-       if (conf.udp_mcast_iface_given) {
-               char *iface = conf.udp_mcast_iface_arg;
+       if (OPT_GIVEN(UDP_MCAST_IFACE)) {
+               const char *iface = OPT_STRING_VAL(UDP_MCAST_IFACE);
 
                id = if_nametoindex(iface);
                if (id == 0)
@@ -167,6 +169,13 @@ static void udp_shutdown_targets(void)
                udp_close_target(sc);
 }
 
+static void udp_shutdown(void)
+{
+       struct sender_client *sc, *tmp;
+       list_for_each_entry_safe(sc, tmp, &targets, node)
+               udp_delete_target(sc, "shutdown");
+}
+
 static int udp_resolve_target(const char *url, struct sender_command_data *scd)
 {
        const char *result;
@@ -175,7 +184,7 @@ static int udp_resolve_target(const char *url, struct sender_command_data *scd)
        ret = parse_fec_url(url, scd);
        if (ret)
                return ret;
-       port = scd->port > 0 ? scd->port : conf.udp_default_port_arg;
+       port = scd->port > 0 ? scd->port : OPT_UINT32_VAL(UDP_DEFAULT_PORT);
 
        ret = para_connect_simple(IPPROTO_UDP, scd->host, port);
        if (ret < 0)
@@ -373,7 +382,7 @@ static char *udp_status(void)
                "port: %s\n"
                "targets: %s\n",
                (sender_status == SENDER_on)? "on" : "off",
-               stringify_port(conf.udp_default_port_arg, "udp"),
+               stringify_port(OPT_UINT32_VAL(UDP_DEFAULT_PORT), "udp"),
                tgts? tgts : "(none)"
        );
        free(tgts);
@@ -386,10 +395,11 @@ static void udp_init_target_list(void)
        int i;
 
        INIT_LIST_HEAD(&targets);
-       for (i = 0; i < conf.udp_target_given; i++) {
-               if (udp_resolve_target(conf.udp_target_arg[i], &scd) < 0)
+       for (i = 0; i < OPT_GIVEN(UDP_TARGET); i++) {
+               const char *arg = lls_string_val(i, OPT_RESULT(UDP_TARGET));
+               if (udp_resolve_target(arg, &scd) < 0)
                        PARA_CRIT_LOG("not adding requested target '%s'\n",
-                                       conf.udp_target_arg[i]);
+                                       arg);
                else
                        udp_com_add(&scd);
        }
@@ -414,32 +424,38 @@ static char *udp_help(void)
        );
 }
 
-/**
- * The init function of para_server's udp sender.
- *
- * \param s Pointer to the udp sender struct.
- *
- * It initializes all function pointers of \a s and the list of udp targets.
- */
-void udp_send_init(struct sender *s)
+/* Initialize the list of udp targets. */
+static void udp_send_init(void)
 {
        INIT_LIST_HEAD(&targets);
-       s->status = udp_status;
-       s->help = udp_help;
-       s->send = NULL;
-       s->pre_select = NULL;
-       s->post_select = NULL;
-       s->shutdown_clients = udp_shutdown_targets;
-       s->resolve_target = udp_resolve_target;
-       s->client_cmds[SENDER_on] = udp_com_on;
-       s->client_cmds[SENDER_off] = udp_com_off;
-       s->client_cmds[SENDER_deny] = NULL;
-       s->client_cmds[SENDER_allow] = NULL;
-       s->client_cmds[SENDER_add] = udp_com_add;
-       s->client_cmds[SENDER_delete] = udp_com_delete;
        sender_status = SENDER_off;
        udp_init_target_list();
-       if (!conf.udp_no_autostart_given)
+       if (!OPT_GIVEN(UDP_NO_AUTOSTART))
                sender_status = SENDER_on;
        PARA_DEBUG_LOG("udp sender init complete\n");
 }
+
+/**
+ * The UDP sender.
+ *
+ * In contrast to the other senders the UDP sender is active in the sense that
+ * it initiates the network connection according to its list of targets rather
+ * than passively waiting for clients to connect. Like DCCP streams, UDP
+ * streams are always sent FEC-encoded. The UDP sender is the only sender which
+ * supports IP multicasting.
+ */
+const struct sender udp_sender = {
+       .name = "udp",
+       .init = udp_send_init,
+       .shutdown = udp_shutdown,
+       .shutdown_clients = udp_shutdown_targets,
+       .resolve_target = udp_resolve_target,
+       .client_cmds = {
+               [SENDER_on] = udp_com_on,
+               [SENDER_off] = udp_com_off,
+               [SENDER_add] = udp_com_add,
+               [SENDER_delete] = udp_com_delete,
+       },
+       .help = udp_help,
+       .status = udp_status,
+};
index 9c751244339c34197d52229b75b4d55e5a58a43f..32a4309d4360fa73a8e7d0bbef622a7928001bb0 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file user_list.c User handling for para_server. */
 
 #include "list.h"
 #include "user_list.h"
 
-static struct list_head user_list;
+static INITIALIZED_LIST_HEAD(user_list);
 
 /*
- * Fill the list of users known to para_server.
+ * Wrapper for fgets(3).
+ *
+ * Unlike fgets(3), an integer value is returned. On success, this function
+ * returns 1. On errors, -E_FGETS is returned. A zero return value indicates an
+ * end of file condition.
+ */
+static int xfgets(char *line, int size, FILE *f)
+{
+again:
+       if (fgets(line, size, f))
+               return 1;
+       if (feof(f))
+               return 0;
+       if (!ferror(f))
+               return -E_FGETS;
+       if (errno != EINTR) {
+               PARA_ERROR_LOG("%s\n", strerror(errno));
+               return -E_FGETS;
+       }
+       clearerr(f);
+       goto again;
+}
+
+/**
+ * Remove all entries from the user list.
+ *
+ * This is called on shutdown and when the user list is reloaded because the
+ * server received SIGHUP.
+ */
+void user_list_deplete(void)
+{
+       struct user *u, *tmpu;
+
+       list_for_each_entry_safe(u, tmpu, &user_list, node) {
+               list_del(&u->node);
+               free(u->name);
+               apc_free_pubkey(u->pubkey);
+               free(u);
+       }
+}
+
+/**
+ * Initialize the list of users allowed to connect to para_server.
+ *
+ * \param user_list_file The file containing access information.
  *
- * Populates a linked list of all users in \a user_list_file.  Returns on
- * success, calls exit() on errors.
+ * If this function is called for the second time, the contents of the
+ * previous call are discarded, i.e. the user list is reloaded.
+ *
+ * This function either succeeds or calls exit(3).
  */
-static void populate_user_list(char *user_list_file)
+void user_list_init(const char *user_list_file)
 {
        int ret = -E_USERLIST;
        FILE *file_ptr = fopen(user_list_file, "r");
+       struct user *u;
 
        if (!file_ptr)
                goto err;
+
+       user_list_deplete();
        for (;;) {
                int num;
                char line[255];
                /* keyword, name, key, perms */
                char w[255], n[255], k[255], p[255], tmp[4][255];
-               struct user *u;
                struct asymmetric_key *pubkey;
 
-               ret = para_fgets(line, sizeof(line), file_ptr);
+               ret = xfgets(line, sizeof(line), file_ptr);
                if (ret <= 0)
                        break;
                if (sscanf(line,"%200s %200s %200s %200s", w, n, k, p) < 3)
@@ -48,22 +92,22 @@ static void populate_user_list(char *user_list_file)
                if (strcmp(w, "user"))
                        continue;
                PARA_DEBUG_LOG("found entry for user %s\n", n);
-               ret = get_asymmetric_key(k, LOAD_PUBLIC_KEY, &pubkey);
+               ret = apc_get_pubkey(k, &pubkey);
                if (ret < 0) {
                        PARA_NOTICE_LOG("skipping entry for user %s: %s\n", n,
                                para_strerror(-ret));
                        continue;
                }
                /*
-                * In order to encrypt len := CHALLENGE_SIZE + 2 * SESSION_KEY_LEN
+                * In order to encrypt len := APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN
                 * bytes using RSA_public_encrypt() with EME-OAEP padding mode,
                 * RSA_size(rsa) must be greater than len + 41. So ignore keys
                 * which are too short. For details see RSA_public_encrypt(3).
                 */
-               if (ret <= CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) {
+               if (ret <= APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) {
                        PARA_WARNING_LOG("public key %s too short (%d)\n",
                                k, ret);
-                       free_asymmetric_key(pubkey);
+                       apc_free_pubkey(pubkey);
                        continue;
                }
                u = para_malloc(sizeof(*u));
@@ -97,32 +141,6 @@ err:
        exit(EXIT_FAILURE);
 }
 
-/**
- * Initialize the list of users allowed to connect to to para_server.
- *
- * \param user_list_file The file containing access information.
- *
- * If this function is called for the second time, the contents of the
- * previous call are discarded, i.e. the user list is reloaded.
- */
-void init_user_list(char *user_list_file)
-{
-       struct user *u, *tmp;
-       static int initialized;
-
-       if (initialized) {
-               list_for_each_entry_safe(u, tmp, &user_list, node) {
-                       list_del(&u->node);
-                       free(u->name);
-                       free_asymmetric_key(u->pubkey);
-                       free(u);
-               }
-       } else
-               INIT_LIST_HEAD(&user_list);
-       initialized = 1;
-       populate_user_list(user_list_file);
-}
-
 /**
  * Lookup a user in the user list.
  *
@@ -131,9 +149,9 @@ void init_user_list(char *user_list_file)
  * \return A pointer to the corresponding user struct if the user was found, \p
  * NULL otherwise.
  */
-struct user *lookup_user(const char *name)
+const struct user *user_list_lookup(const char *name)
 {
-       struct user *u;
+       const struct user *u;
        list_for_each_entry(u, &user_list, node) {
                if (strcmp(u->name, name))
                        continue;
index 3a77e98fa8c4636406035ce66f7842235832db4d..1cb94764cca42341e9a48b576491e12a5630d888 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file user_list.h exported functions from user_list.c */
 
@@ -15,6 +11,7 @@
  * is read at server startup.
  */
 enum server_command_permissions {
+       NO_PERMISSION_REQUIRED = 0, /** None of the below. */
        AFS_READ = 1, /** Read-only operation on the AFS database. */
        AFS_WRITE = 2, /** Read-write operation on the AFS database. */
        VSS_READ = 4, /** Read-only operation on the virtual streaming system. */
@@ -35,5 +32,6 @@ struct user {
        unsigned int perms;
 };
 
-void init_user_list(char *user_list_file);
-struct user *lookup_user(const char *name);
+void user_list_init(const char *user_list_file);
+void user_list_deplete(void);
+const struct user *user_list_lookup(const char *name);
index bc61f54bba03aaa5a3255c6831b1bf8d6e9d2324..d057f9c3b8310505215ba9c183a7deac710290a9 100644 (file)
--- a/version.c
+++ b/version.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2013 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2013 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file version.c Some helpers for printing version and copyright strings. */
 
diff --git a/vss.c b/vss.c
index 4d73a95cd142908f26ec9c2108139f1561a4c995..9969a150ee63291e182f058664af9178eabf77f3 100644 (file)
--- a/vss.c
+++ b/vss.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file vss.c The virtual streaming system.
  *
@@ -19,7 +15,9 @@
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
+#include "server.lsg.h"
 #include "para.h"
 #include "error.h"
 #include "portable_io.h"
@@ -27,9 +25,8 @@
 #include "string.h"
 #include "afh.h"
 #include "afs.h"
-#include "server.h"
 #include "net.h"
-#include "server.cmdline.h"
+#include "server.h"
 #include "list.h"
 #include "send.h"
 #include "sched.h"
 #include "fd.h"
 
 extern struct misc_meta_data *mmd;
-
-extern void dccp_send_init(struct sender *);
-extern void http_send_init(struct sender *);
-extern void udp_send_init(struct sender *);
-
-/** The list of supported senders. */
-struct sender senders[] = {
-       {
-               .name = "http",
-               .init = http_send_init,
-       },
-       {
-               .name = "dccp",
-               .init = dccp_send_init,
-       },
-       {
-               .name = "udp",
-               .init = udp_send_init,
-       },
-       {
-               .name = NULL,
-       }
-};
+extern const struct sender udp_sender, dccp_sender, http_sender;
+const struct sender * const senders[] = {
+       &http_sender, &dccp_sender, &udp_sender, NULL};
 
 /** The possible states of the afs socket. */
 enum afs_socket_status {
@@ -88,6 +65,8 @@ struct vss_task {
        enum afs_socket_status afsss;
        /** The memory mapped audio file. */
        char *map;
+       /** The size of the memory mapping. */
+       size_t mapsize;
        /** Used by the scheduler. */
        struct task *task;
        /** Pointer to the header of the mapped audio file. */
@@ -96,6 +75,8 @@ struct vss_task {
        size_t header_len;
        /** Time between audio file headers are sent. */
        struct timeval header_interval;
+       /* Only used if afh supports dynamic chunks. */
+       void *afh_context;
 };
 
 /**
@@ -167,14 +148,10 @@ struct fec_client {
        struct fec_group group;
        /** The current slice. */
        uint8_t current_slice_num;
-       /** The data to be FEC-encoded (point to a region within the mapped audio file). */
-       const unsigned char **src_data;
+       /** The data to be FEC-encoded */
+       unsigned char **src_data;
        /** Last time an audio  header was sent. */
        struct timeval next_header_time;
-       /** Used for the last source pointer of an audio file. */
-       unsigned char *extra_src_buf;
-       /** Needed for the last slice of the audio file header. */
-       unsigned char *extra_header_buf;
        /** Extra slices needed to store largest chunk + header. */
        int num_extra_slices;
        /** Contains the FEC-encoded data. */
@@ -259,6 +236,21 @@ static bool need_data_slices(struct fec_client *fc, struct vss_task *vsst)
        return false;
 }
 
+static int fc_num_data_slices(const struct fec_client *fc)
+{
+       return fc->fcp->data_slices_per_group + fc->num_extra_slices;
+}
+
+static int fc_num_slices(const struct fec_client *fc)
+{
+       return fc->fcp->slices_per_group + fc->num_extra_slices;
+}
+
+static int fc_num_redundant_slices(const struct fec_client *fc)
+{
+       return fc->fcp->slices_per_group - fc->fcp->data_slices_per_group;
+}
+
 static int num_slices(long unsigned bytes, int max_payload, int rs)
 {
        int ret;
@@ -289,7 +281,7 @@ static void set_group_timing(struct fec_client *fc, struct vss_task *vsst)
 
 static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
 {
-       int k, n, ret;
+       int i, k, n, ret;
        int hs, ds, rs; /* header/data/redundant slices */
        struct fec_client_parms *fcp = fc->fcp;
 
@@ -297,7 +289,7 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
        if (fcp->init_fec) {
                /*
                 * Set the maximum slice size to the Maximum Packet Size if the
-                * transport protocol allows to determine this value. The user
+                * transport protocol allows determination of this value. The user
                 * can specify a slice size up to this value.
                 */
                ret = fcp->init_fec(fc->sc);
@@ -309,7 +301,17 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
        if (fc->mps <= FEC_HEADER_SIZE)
                return -ERRNO_TO_PARA_ERROR(EINVAL);
 
-       rs = fc->fcp->slices_per_group - fc->fcp->data_slices_per_group;
+       /* free previous buffers, if any */
+       if (fc->src_data) {
+               k = fc_num_data_slices(fc);
+               for (i = 0; i < k; i++)
+                       free(fc->src_data[i]);
+               free(fc->src_data);
+               fc->src_data = NULL;
+       }
+       free(fc->enc_buf);
+
+       rs = fc_num_redundant_slices(fc);
        ret = num_slices(vsst->header_len, fc->mps - FEC_HEADER_SIZE, rs);
        if (ret < 0)
                return ret;
@@ -325,17 +327,18 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
        if (k < fc->fcp->data_slices_per_group)
                k = fc->fcp->data_slices_per_group;
        fc->num_extra_slices = k - fc->fcp->data_slices_per_group;
-       n = k + rs;
+       n = fc_num_slices(fc);
+       PARA_INFO_LOG("mps: %d, k: %d, n: %d, extra slices: %d\n",
+               fc->mps, k, n, fc->num_extra_slices);
+
        fec_free(fc->parms);
        ret = fec_new(k, n, &fc->parms);
        if (ret < 0)
                return ret;
-       PARA_INFO_LOG("mps: %d, k: %d, n: %d, extra slices: %d\n",
-               fc->mps, k, n, fc->num_extra_slices);
-       fc->src_data = para_realloc(fc->src_data, k * sizeof(char *));
-       fc->enc_buf = para_realloc(fc->enc_buf, fc->mps);
-       fc->extra_src_buf = para_realloc(fc->extra_src_buf, fc->mps);
-       fc->extra_header_buf = para_realloc(fc->extra_header_buf, fc->mps);
+       fc->src_data = para_malloc(k * sizeof(char *));
+       for (i = 0; i < k; i++)
+               fc->src_data[i] = para_malloc(fc->mps);
+       fc->enc_buf = para_malloc(fc->mps);
 
        fc->state = FEC_STATE_READY_TO_RUN;
        fc->next_header_time.tv_sec = 0;
@@ -344,9 +347,11 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
        return 1;
 }
 
-static void vss_get_chunk(int chunk_num, struct vss_task *vsst,
+static int vss_get_chunk(int chunk_num, struct vss_task *vsst,
                char **buf, size_t *sz)
 {
+       int ret;
+
        /*
         * Chunk zero is special for header streams: It is the first portion of
         * the audio file which consists of the audio file header. It may be
@@ -356,26 +361,35 @@ static void vss_get_chunk(int chunk_num, struct vss_task *vsst,
         * rather than the unmodified header (chunk zero).
         */
        if (chunk_num == 0 && vsst->header_len > 0) {
+               assert(vsst->header_buf);
                *buf = vsst->header_buf; /* stripped header */
                *sz = vsst->header_len;
-               return;
+               return 0;
+       }
+       ret = afh_get_chunk(chunk_num, &mmd->afd.afhi,
+               mmd->afd.audio_format_id, vsst->map, vsst->mapsize,
+               (const char **)buf, sz, &vsst->afh_context);
+       if (ret < 0) {
+               *buf = NULL;
+               *sz = 0;
        }
-       afh_get_chunk(chunk_num, &mmd->afd.afhi, vsst->map, (const char **)buf,
-               sz);
+       return ret;
 }
 
-static void compute_group_size(struct vss_task *vsst, struct fec_group *g,
+static int compute_group_size(struct vss_task *vsst, struct fec_group *g,
                int max_bytes)
 {
        char *buf;
        size_t len;
-       int i, max_chunks = PARA_MAX(1LU, 150 / tv2ms(vss_chunk_time()));
+       int ret, i, max_chunks = PARA_MAX(1LU, 150 / tv2ms(vss_chunk_time()));
 
        if (g->first_chunk == 0) {
                g->num_chunks = 1;
-               vss_get_chunk(0, vsst, &buf, &len);
+               ret = vss_get_chunk(0, vsst, &buf, &len);
+               if (ret < 0)
+                        return ret;
                g->bytes = len;
-               return;
+               return 0;
        }
 
        g->num_chunks = 0;
@@ -393,14 +407,20 @@ static void compute_group_size(struct vss_task *vsst, struct fec_group *g,
                        break;
                if (chunk_num >= mmd->afd.afhi.chunks_total) /* eof */
                        break;
-               vss_get_chunk(chunk_num, vsst, &buf, &len);
+               ret = vss_get_chunk(chunk_num, vsst, &buf, &len);
+               if (ret < 0)
+                        return ret;
                if (g->bytes + len > max_bytes)
                        break;
                /* Include this chunk */
                g->bytes += len;
                g->num_chunks++;
        }
-       assert(g->num_chunks);
+       if (g->num_chunks == 0)
+               return -E_EOF;
+       PARA_DEBUG_LOG("group #%u: %u chunks, %u bytes total\n", g->num,
+               g->num_chunks, g->bytes);
+       return 1;
 }
 
 /*
@@ -453,8 +473,8 @@ static void compute_group_size(struct vss_task *vsst, struct fec_group *g,
 static int compute_slice_size(struct fec_client *fc, struct vss_task *vsst)
 {
        struct fec_group *g = &fc->group;
-       int k = fc->fcp->data_slices_per_group + fc->num_extra_slices;
-       int n = fc->fcp->slices_per_group + fc->num_extra_slices;
+       int k = fc_num_data_slices(fc);
+       int n = fc_num_slices(fc);
        int ret, k1, k2, h, d, min, max, sum;
        int max_slice_bytes = fc->mps - FEC_HEADER_SIZE;
        int max_group_bytes;
@@ -462,7 +482,9 @@ static int compute_slice_size(struct fec_client *fc, struct vss_task *vsst)
        if (!need_audio_header(fc, vsst)) {
                max_group_bytes = k * max_slice_bytes;
                g->num_header_slices = 0;
-               compute_group_size(vsst, g, max_group_bytes);
+               ret = compute_group_size(vsst, g, max_group_bytes);
+               if (ret < 0)
+                       return ret;
                g->slice_bytes = DIV_ROUND_UP(g->bytes, k);
                if (g->slice_bytes == 0)
                        g->slice_bytes = 1;
@@ -478,7 +500,9 @@ static int compute_slice_size(struct fec_client *fc, struct vss_task *vsst)
        h = vsst->header_len;
        max_group_bytes = (k - num_slices(h, max_slice_bytes, n - k))
                * max_slice_bytes;
-       compute_group_size(vsst, g, max_group_bytes);
+       ret = compute_group_size(vsst, g, max_group_bytes);
+       if (ret < 0)
+               return ret;
        d = g->bytes;
        if (d == 0) {
                g->slice_bytes = DIV_ROUND_UP(h, k);
@@ -513,9 +537,8 @@ static int compute_slice_size(struct fec_client *fc, struct vss_task *vsst)
 
 static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
 {
-       int ret, i, k, n, data_slices;
-       size_t len;
-       char *buf, *p;
+       int ret, i, c;
+       size_t copy, src_copied, slice_copied;
        struct fec_group *g = &fc->group;
 
        if (fc->state == FEC_STATE_NONE) {
@@ -539,84 +562,63 @@ static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst)
                g->first_chunk += g->num_chunks;
                g->num++;
        }
-       k = fc->fcp->data_slices_per_group + fc->num_extra_slices;
-       n = fc->fcp->slices_per_group + fc->num_extra_slices;
-
        compute_slice_size(fc, vsst);
        assert(g->slice_bytes > 0);
-       ret = num_slices(g->bytes, g->slice_bytes, n - k);
-       if (ret < 0)
-               return ret;
-       data_slices = ret;
-       assert(g->num_header_slices + data_slices <= k);
        fc->current_slice_num = 0;
        if (g->num == 0)
                set_group_timing(fc, vsst);
        /* setup header slices */
-       buf = vsst->header_buf;
-       for (i = 0; i < g->num_header_slices; i++) {
-               uint32_t payload_size;
-               if (buf + g->slice_bytes <= vsst->header_buf + vsst->header_len) {
-                       fc->src_data[i] = (const unsigned char *)buf;
-                       buf += g->slice_bytes;
-                       continue;
-               }
-               /*
-                * Can not use vss->header_buf for this slice as it
-                * goes beyond the buffer. This slice will not be fully
-                * used.
-                */
-               payload_size = vsst->header_buf + vsst->header_len - buf;
-               memcpy(fc->extra_header_buf, buf, payload_size);
-               if (payload_size < g->slice_bytes)
-                       memset(fc->extra_header_buf + payload_size, 0,
-                               g->slice_bytes - payload_size);
-               /*
-                * There might be more than one header slice to fill although
-                * only the first one will be used. Set all header slices to
-                * our extra buffer.
-                */
-               while (i < g->num_header_slices)
-                       fc->src_data[i++] = fc->extra_header_buf;
-               break; /* we don't want i to be increased. */
+       for (i = 0, src_copied = 0; i < g->num_header_slices; i++) {
+               copy = PARA_MIN((size_t)g->slice_bytes, vsst->header_len - src_copied);
+               if (copy == 0)
+                       break;
+               memcpy(fc->src_data[i], vsst->header_buf + src_copied, copy);
+               if (copy < g->slice_bytes)
+                       memset(fc->src_data[i] + copy, 0, g->slice_bytes - copy);
+               src_copied += copy;
        }
-
        /*
-        * Setup data slices. Note that for ogg streams chunk 0 points to a
-        * buffer on the heap rather than to the mapped audio file.
+        * There might be more than one header slice to fill although only the
+        * first one will be used. Zero out any remaining header slices.
         */
-       vss_get_chunk(g->first_chunk, vsst, &buf, &len);
-       for (p = buf; i < g->num_header_slices + data_slices; i++) {
-               if (p + g->slice_bytes > buf + g->bytes) {
-                       /*
-                        * We must make a copy for this slice since using p
-                        * directly would exceed the buffer.
-                        */
-                       uint32_t payload_size = buf + g->bytes - p;
-                       assert(payload_size + FEC_HEADER_SIZE <= fc->mps);
-                       memcpy(fc->extra_src_buf, p, payload_size);
-                       if (payload_size < g->slice_bytes)
-                               memset(fc->extra_src_buf + payload_size, 0,
-                                       g->slice_bytes - payload_size);
-                       fc->src_data[i] = fc->extra_src_buf;
-                       i++;
-                       break;
+       while (i < g->num_header_slices)
+               memset(fc->src_data[i++], 0, g->slice_bytes);
+
+       slice_copied = 0;
+       for (c = g->first_chunk; c < g->first_chunk + g->num_chunks; c++) {
+               char *buf;
+               size_t src_len;
+               ret = vss_get_chunk(c, vsst, &buf, &src_len);
+               if (ret < 0)
+                       return ret;
+               if (src_len == 0)
+                       continue;
+               src_copied = 0;
+               while (src_copied < src_len) {
+                       copy = PARA_MIN((size_t)g->slice_bytes - slice_copied,
+                               src_len - src_copied);
+                       memcpy(fc->src_data[i] + slice_copied,
+                               buf + src_copied, copy);
+                       src_copied += copy;
+                       slice_copied += copy;
+                       if (slice_copied == g->slice_bytes) {
+                               i++;
+                               slice_copied = 0;
+                       }
                }
-               fc->src_data[i] = (const unsigned char *)p;
-               p += g->slice_bytes;
-       }
-       if (i < k) {
-               /* use arbitrary data for all remaining slices */
-               buf = vsst->map;
-               for (; i < k; i++)
-                       fc->src_data[i] = (const unsigned char *)buf;
        }
+       if (i < fc_num_data_slices(fc) && slice_copied < g->slice_bytes)
+               memset(fc->src_data[i] + slice_copied, 0,
+                        g->slice_bytes - slice_copied);
+       /* zero out remaining slices, if any */
+       while (++i < fc_num_data_slices(fc))
+               memset(fc->src_data[i], 0, g->slice_bytes);
        PARA_DEBUG_LOG("FEC group %u: %u chunks (%u - %u), %u bytes\n",
                g->num, g->num_chunks, g->first_chunk,
                g->first_chunk + g->num_chunks - 1, g->bytes
        );
        PARA_DEBUG_LOG("slice_bytes: %d, %d header slices, %d data slices\n",
-               g->slice_bytes, g->num_header_slices, data_slices
+               g->slice_bytes, g->num_header_slices, fc_num_data_slices(fc)
        );
        return 1;
 }
@@ -636,8 +638,9 @@ static int compute_next_fec_slice(struct fec_client *fc, struct vss_task *vsst)
                }
        }
        write_fec_header(fc, vsst);
-       fec_encode(fc->parms, fc->src_data, fc->enc_buf + FEC_HEADER_SIZE,
-               fc->current_slice_num, fc->group.slice_bytes);
+       fec_encode(fc->parms, (const unsigned char * const*)fc->src_data,
+               fc->enc_buf + FEC_HEADER_SIZE, fc->current_slice_num,
+               fc->group.slice_bytes);
        return 1;
 }
 
@@ -683,11 +686,15 @@ struct fec_client *vss_add_fec_client(struct sender_client *sc,
  */
 void vss_del_fec_client(struct fec_client *fc)
 {
+       int i;
+
        list_del(&fc->node);
-       free(fc->src_data);
        free(fc->enc_buf);
-       free(fc->extra_src_buf);
-       free(fc->extra_header_buf);
+       if (fc->src_data) {
+               for (i = 0; i < fc_num_data_slices(fc); i++)
+                       free(fc->src_data[i]);
+               free(fc->src_data);
+       }
        fec_free(fc->parms);
        free(fc);
 }
@@ -846,7 +853,7 @@ static void vss_eof(struct vss_task *vsst)
        set_eof_barrier(vsst);
        afh_free_header(vsst->header_buf, mmd->afd.audio_format_id);
        vsst->header_buf = NULL;
-       para_munmap(vsst->map, mmd->size);
+       para_munmap(vsst->map, vsst->mapsize);
        vsst->map = NULL;
        mmd->chunks_sent = 0;
        //mmd->offset = 0;
@@ -855,7 +862,9 @@ static void vss_eof(struct vss_task *vsst)
        mmd->afd.afhi.chunk_tv.tv_usec = 0;
        free(mmd->afd.afhi.chunk_table);
        mmd->afd.afhi.chunk_table = NULL;
-       mmd->size = 0;
+       vsst->mapsize = 0;
+       afh_close(vsst->afh_context, mmd->afd.audio_format_id);
+       vsst->afh_context = NULL;
        mmd->events++;
 }
 
@@ -895,10 +904,10 @@ static void vss_pre_select(struct sched *s, void *context)
                vsst->afsss = AFS_SOCKET_CHECK_FOR_WRITE;
        } else
                para_fd_set(vsst->afs_socket, &s->rfds, &s->max_fileno);
-       for (i = 0; senders[i].name; i++) {
-               if (!senders[i].pre_select)
+       FOR_EACH_SENDER(i) {
+               if (!senders[i]->pre_select)
                        continue;
-               senders[i].pre_select(&s->max_fileno, &s->rfds, &s->wfds);
+               senders[i]->pre_select(&s->max_fileno, &s->rfds, &s->wfds);
        }
        vss_compute_timeout(s, vsst);
 }
@@ -938,6 +947,7 @@ static int recv_afs_msg(int afs_socket, int *fd, uint32_t *code, uint32_t *data)
 }
 
 #ifndef MAP_POPULATE
+/** As of 2018, neither FreeBSD-11.2 nor NetBSD-8.0 have MAP_POPULATE. */
 #define MAP_POPULATE 0
 #endif
 
@@ -955,11 +965,17 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
        if (ret < 0)
                goto err;
        vsst->afsss = AFS_SOCKET_READY;
-       PARA_DEBUG_LOG("fd: %d, code: %u, shmid: %u\n", passed_fd, afs_code,
-               afs_data);
+       if (afs_code == NO_ADMISSIBLE_FILES) {
+               PARA_NOTICE_LOG("no admissible files\n");
+               ret = 0;
+               goto err;
+       }
        ret = -E_NOFD;
-       if (afs_code != NEXT_AUDIO_FILE)
+       if (afs_code != NEXT_AUDIO_FILE) {
+               PARA_ERROR_LOG("afs code: %u, expected: %d\n", afs_code,
+                       NEXT_AUDIO_FILE);
                goto err;
+       }
        if (passed_fd < 0)
                goto err;
        shmid = afs_data;
@@ -973,11 +989,11 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
                ret = -ERRNO_TO_PARA_ERROR(errno);
                goto err;
        }
-       mmd->size = statbuf.st_size;
-       ret = para_mmap(mmd->size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
-               passed_fd, 0, &vsst->map);
+       ret = para_mmap(statbuf.st_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
+               passed_fd, &vsst->map);
        if (ret < 0)
                goto err;
+       vsst->mapsize = statbuf.st_size;
        close(passed_fd);
        mmd->chunks_sent = 0;
        mmd->current_chunk = 0;
@@ -986,13 +1002,15 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
        mmd->num_played++;
        mmd->new_vss_status_flags &= (~VSS_NEXT);
        afh_get_header(&mmd->afd.afhi, mmd->afd.audio_format_id,
-               vsst->map, mmd->size, &vsst->header_buf, &vsst->header_len);
+               vsst->map, vsst->mapsize, &vsst->header_buf, &vsst->header_len);
        return;
 err:
        free(mmd->afd.afhi.chunk_table);
+       mmd->afd.afhi.chunk_table = NULL;
        if (passed_fd >= 0)
                close(passed_fd);
-       PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       if (ret < 0)
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
        mmd->new_vss_status_flags = VSS_NEXT;
 }
 
@@ -1007,22 +1025,24 @@ err:
  */
 static void vss_send(struct vss_task *vsst)
 {
-       int i, fec_active = 0;
+       int i, ret;
+       bool fec_active = false;
        struct timeval due;
        struct fec_client *fc, *tmp_fc;
+       char *buf;
+       size_t len;
 
        if (!vsst->map || !vss_playing())
                return;
        if (chk_barrier("eof", &vsst->eof_barrier, &due, 1) < 0)
                return;
-       if (chk_barrier("data send", &vsst->data_send_barrier,
-                       &due, 1) < 0)
+       if (chk_barrier("data send", &vsst->data_send_barrier, &due, 1) < 0)
                return;
        list_for_each_entry_safe(fc, tmp_fc, &fec_client_list, node) {
                if (fc->state == FEC_STATE_DISABLED)
                        continue;
                if (!next_slice_is_due(fc, NULL)) {
-                       fec_active = 1;
+                       fec_active = true;
                        continue;
                }
                if (compute_next_fec_slice(fc, vsst) <= 0)
@@ -1032,7 +1052,7 @@ static void vss_send(struct vss_task *vsst)
                fc->current_slice_num++;
                fc->fcp->send_fec(fc->sc, (char *)fc->enc_buf,
                        fc->group.slice_bytes + FEC_HEADER_SIZE);
-               fec_active = 1;
+               fec_active = true;
        }
        if (mmd->current_chunk >= mmd->afd.afhi.chunks_total) { /* eof */
                if (!fec_active)
@@ -1041,49 +1061,31 @@ static void vss_send(struct vss_task *vsst)
        }
        compute_chunk_time(mmd->chunks_sent, &mmd->afd.afhi.chunk_tv,
                &mmd->stream_start, &due);
-       if (tv_diff(&due, now, NULL) <= 0) {
-               char *buf;
-               size_t len;
-
-               if (!mmd->chunks_sent) {
-                       mmd->stream_start = *now;
-                       mmd->events++;
-                       set_mmd_offset();
-               }
+       if (tv_diff(&due, now, NULL) > 0)
+               return;
+       if (!mmd->chunks_sent) {
+               mmd->stream_start = *now;
+               mmd->events++;
+               set_mmd_offset();
+       }
+       ret = vss_get_chunk(mmd->current_chunk, vsst, &buf, &len);
+       if (ret < 0) {
+               PARA_ERROR_LOG("could not get chunk %lu: %s\n",
+                       mmd->current_chunk, para_strerror(-ret));
+       } else {
                /*
-                * We call the send function also in case of empty chunks as
-                * they might have still some data queued which can be sent in
-                * this case.
+                * We call ->send() even if len is zero because senders might
+                * have data queued which can be sent now.
                 */
-               vss_get_chunk(mmd->current_chunk, vsst, &buf, &len);
-               for (i = 0; senders[i].name; i++) {
-                       if (!senders[i].send)
+               FOR_EACH_SENDER(i) {
+                       if (!senders[i]->send)
                                continue;
-                       senders[i].send(mmd->current_chunk, mmd->chunks_sent,
+                       senders[i]->send(mmd->current_chunk, mmd->chunks_sent,
                                buf, len, vsst->header_buf, vsst->header_len);
                }
-               /*
-                * Prefault next chunk(s)
-                *
-                * If the backing device of the memory-mapped audio file is
-                * slow and read-ahead is turned off or prevented for some
-                * reason, e.g. due to memory pressure, it may take much longer
-                * than the chunk interval to get the next chunk on the wire,
-                * causing buffer underruns on the client side. Mapping the
-                * file with MAP_POPULATE seems to help a bit, but it does not
-                * eliminate the delays completely. Moreover, it is supported
-                * only on Linux. So we do our own read-ahead here.
-                */
-               if (mmd->current_chunk > 0) { /* chunk 0 might be on the heap */
-                       buf += len;
-                       for (i = 0; i < 5 && buf < vsst->map + mmd->size; i++) {
-                               __a_unused volatile char x = *buf;
-                               buf += 4096;
-                       }
-               }
-               mmd->chunks_sent++;
-               mmd->current_chunk++;
        }
+       mmd->chunks_sent++;
+       mmd->current_chunk++;
 }
 
 static int vss_post_select(struct sched *s, void *context)
@@ -1091,12 +1093,17 @@ static int vss_post_select(struct sched *s, void *context)
        int ret, i;
        struct vss_task *vsst = context;
 
+       ret = task_get_notification(vsst->task);
+       if (ret < 0) {
+               afh_free_header(vsst->header_buf, mmd->afd.audio_format_id);
+               return ret;
+       }
        if (!vsst->map || vss_next() || vss_paused() || vss_repos()) {
                /* shut down senders and fec clients */
                struct fec_client *fc, *tmp;
-               for (i = 0; senders[i].name; i++)
-                       if (senders[i].shutdown_clients)
-                               senders[i].shutdown_clients();
+               FOR_EACH_SENDER(i)
+                       if (senders[i]->shutdown_clients)
+                               senders[i]->shutdown_clients();
                list_for_each_entry_safe(fc, tmp, &fec_client_list, node)
                        fc->state = FEC_STATE_NONE;
                mmd->stream_start.tv_sec = 0;
@@ -1113,7 +1120,7 @@ static int vss_post_select(struct sched *s, void *context)
                set_eof_barrier(vsst);
                mmd->chunks_sent = 0;
                mmd->current_chunk = afh_get_start_chunk(mmd->repos_request,
-                       &mmd->afd.afhi);
+                       &mmd->afd.afhi, mmd->afd.audio_format_id);
                mmd->new_vss_status_flags &= ~VSS_REPOS;
                set_mmd_offset();
        }
@@ -1122,8 +1129,8 @@ static int vss_post_select(struct sched *s, void *context)
                int num = mmd->sender_cmd_data.cmd_num,
                        sender_num = mmd->sender_cmd_data.sender_num;
 
-               if (senders[sender_num].client_cmds[num]) {
-                       ret = senders[sender_num].client_cmds[num]
+               if (senders[sender_num]->client_cmds[num]) {
+                       ret = senders[sender_num]->client_cmds[num]
                                (&mmd->sender_cmd_data);
                        if (ret < 0)
                                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
@@ -1140,10 +1147,10 @@ static int vss_post_select(struct sched *s, void *context)
                else
                        vsst->afsss = AFS_SOCKET_AFD_PENDING;
        }
-       for (i = 0; senders[i].name; i++) {
-               if (!senders[i].post_select)
+       FOR_EACH_SENDER(i) {
+               if (!senders[i]->post_select)
                        continue;
-               senders[i].post_select(&s->rfds, &s->wfds);
+               senders[i]->post_select(&s->rfds, &s->wfds);
        }
        if ((vss_playing() && !(mmd->vss_status_flags & VSS_PLAYING)) ||
                        (vss_next() && vss_playing()))
@@ -1161,28 +1168,23 @@ static int vss_post_select(struct sched *s, void *context)
  * This also initializes all supported senders and starts streaming
  * if the --autoplay command line flag was given.
  */
-void init_vss_task(int afs_socket, struct sched *s)
+void vss_init(int afs_socket, struct sched *s)
 {
        static struct vss_task vss_task_struct, *vsst = &vss_task_struct;
        int i;
-       char *hn = para_hostname(), *home = para_homedir();
-       long unsigned announce_time = conf.announce_time_arg > 0?
-                       conf.announce_time_arg : 300,
-               autoplay_delay = conf.autoplay_delay_arg > 0?
-                       conf.autoplay_delay_arg : 0;
+       long unsigned announce_time = OPT_UINT32_VAL(ANNOUNCE_TIME),
+               autoplay_delay = OPT_UINT32_VAL(AUTOPLAY_DELAY);
        vsst->header_interval.tv_sec = 5; /* should this be configurable? */
        vsst->afs_socket = afs_socket;
        ms2tv(announce_time, &vsst->announce_tv);
        PARA_INFO_LOG("announce timeval: %lums\n", tv2ms(&vsst->announce_tv));
        INIT_LIST_HEAD(&fec_client_list);
-       for (i = 0; senders[i].name; i++) {
-               PARA_NOTICE_LOG("initializing %s sender\n", senders[i].name);
-               senders[i].init(&senders[i]);
+       FOR_EACH_SENDER(i) {
+               PARA_NOTICE_LOG("initializing %s sender\n", senders[i]->name);
+               senders[i]->init();
        }
-       free(hn);
-       free(home);
        mmd->sender_cmd_data.cmd_num = -1;
-       if (conf.autoplay_given) {
+       if (OPT_GIVEN(AUTOPLAY)) {
                struct timeval tmp;
                mmd->vss_status_flags |= VSS_PLAYING;
                mmd->new_vss_status_flags |= VSS_PLAYING;
@@ -1192,9 +1194,26 @@ void init_vss_task(int afs_socket, struct sched *s)
                        &vsst->data_send_barrier);
        }
        vsst->task = task_register(&(struct task_info) {
-               .name = "vss task",
+               .name = "vss",
                .pre_select = vss_pre_select,
                .post_select = vss_post_select,
                .context = vsst,
        }, s);
 }
+
+/**
+ * Turn off the virtual streaming system.
+ *
+ * This is only executed on exit. It calls the ->shutdown method of all senders.
+ */
+void vss_shutdown(void)
+{
+       int i;
+
+       FOR_EACH_SENDER(i) {
+               if (!senders[i]->shutdown)
+                       continue;
+               PARA_NOTICE_LOG("shutting down %s sender\n", senders[i]->name);
+               senders[i]->shutdown();
+       }
+}
diff --git a/vss.h b/vss.h
index d2b1ad1dd2d008420462f7f7c4b53f9ff34ab243..46bb0e7359bf82acc4736dbb061eda5d7faf24f8 100644 (file)
--- a/vss.h
+++ b/vss.h
@@ -1,18 +1,15 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file vss.h Exported functions from vss.c (para_server). */
 
-void init_vss_task(int afs_socket, struct sched *s);
+void vss_init(int afs_socket, struct sched *s);
 unsigned int vss_playing(void);
 unsigned int vss_next(void);
 unsigned int vss_repos(void);
 unsigned int vss_paused(void);
 unsigned int vss_stopped(void);
 struct timeval *vss_chunk_time(void);
+void vss_shutdown(void);
 
 /** Stop playing after current audio file. */
 #define VSS_NOMORE 1
index 88047adbb71c2becef6afe13f45ab96ddd3b1774..e749160d3337e87dc7c908c023ea100c955590e4 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file wav_filter.c A filter that inserts a wave header. */
 
@@ -12,7 +8,6 @@
 #include "error.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "filter.h"
 #include "string.h"
@@ -120,15 +115,9 @@ err:
        return ret;
 }
 
-/**
- * The init function of the wav filter.
- *
- * \param f Structure to initialize.
- */
-void wav_filter_init(struct filter *f)
-{
-       f->close = wav_close;
-       f->open = wav_open;
-       f->pre_select = wav_pre_select;
-       f->post_select = wav_post_select;
-}
+const struct filter lsg_filter_cmd_com_wav_user_data = {
+       .close = wav_close,
+       .open = wav_open,
+       .pre_select = wav_pre_select,
+       .post_select = wav_post_select,
+};
index 425c2190d1798557138f605a9de43b94412f88cd..2b7fe2217b51b1d54fefc2a005b5e3ad509a65d9 100644 (file)
@@ -6,7 +6,7 @@
        AddIcon ../signature.png *.asc
        AddDescription "Digital signature" *.asc
 
-       AddIcon ../tar-icon.png *.tgz *.tar.bz2
-       AddDescription "current master snapshot" -git.tar.bz2 .g*.tar.bz2 .g*.dirty.tar.bz2
-       AddDescription "release tarball" *.tgz *.tar.bz2
+       AddIcon ../tar-icon.png *.tgz *.tar.bz2 *.tar.xz
+       AddDescription "current master snapshot" -git.tar.xz .g*.tar.xz .g*.dirty.tar.xz
+       AddDescription "release tarball" *.tgz *.tar.bz2 *.tar.xz
 </ifmodule>
index 2c72f28126d2ba083ac0de69f643c6b501820987..8395864a00a6e376634272954708cd96739afc2f 100644 (file)
@@ -5,7 +5,7 @@ Paraslash is a collection of network audio streaming tools for Unix
 systems. It is written in C and released under the GPLv2.
 
 <ul>
-       <li> Runs on Linux, Mac OS, FreeBSD, NetBSD </li>
+       <li> Runs on Linux, FreeBSD, NetBSD </li>
        <li> Mp3, ogg/vorbis, ogg/speex, aac (m4a), wma, flac and ogg/opus support </li>
        <li> http, dccp and udp network streaming </li>
        <li> Stand-alone decoder, player, tagger </li>
@@ -17,7 +17,8 @@ systems. It is written in C and released under the GPLv2.
 </ul>
 
 <b> Author: </b> Andr&eacute; Noll,
-<a href="mailto:maan@tuebingen.mpg.de">maan@tuebingen.mpg.de</a>
+<a href="mailto:maan@tuebingen.mpg.de">maan@tuebingen.mpg.de</a>,
+Homepage: <a href="http://people.tuebingen.mpg.de/maan/">http://people.tuebingen.mpg.de/maan/</a>
 <br>
 Comments and bug reports are welcome. Please provide the version of
 paraslash you are using and relevant parts of the logs.
diff --git a/web/dia/overview.dia b/web/dia/overview.dia
deleted file mode 100644 (file)
index f9e0158..0000000
+++ /dev/null
@@ -1,3817 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<dia:diagram xmlns:dia="http://www.lysator.liu.se/~alla/dia/">
-  <dia:diagramdata>
-    <dia:attribute name="background">
-      <dia:color val="#ffffff"/>
-    </dia:attribute>
-    <dia:attribute name="pagebreak">
-      <dia:color val="#000099"/>
-    </dia:attribute>
-    <dia:attribute name="paper">
-      <dia:composite type="paper">
-        <dia:attribute name="name">
-          <dia:string>#A4#</dia:string>
-        </dia:attribute>
-        <dia:attribute name="tmargin">
-          <dia:real val="2.8222000598907471"/>
-        </dia:attribute>
-        <dia:attribute name="bmargin">
-          <dia:real val="2.8222000598907471"/>
-        </dia:attribute>
-        <dia:attribute name="lmargin">
-          <dia:real val="2.8222000598907471"/>
-        </dia:attribute>
-        <dia:attribute name="rmargin">
-          <dia:real val="2.8222000598907471"/>
-        </dia:attribute>
-        <dia:attribute name="is_portrait">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="scaling">
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="fitto">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-      </dia:composite>
-    </dia:attribute>
-    <dia:attribute name="grid">
-      <dia:composite type="grid">
-        <dia:attribute name="width_x">
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="width_y">
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="visible_x">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="visible_y">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:composite type="color"/>
-      </dia:composite>
-    </dia:attribute>
-    <dia:attribute name="color">
-      <dia:color val="#d8e5e5"/>
-    </dia:attribute>
-    <dia:attribute name="guides">
-      <dia:composite type="guides">
-        <dia:attribute name="hguides"/>
-        <dia:attribute name="vguides"/>
-      </dia:composite>
-    </dia:attribute>
-  </dia:diagramdata>
-  <dia:layer name="Background" visible="true" active="true">
-    <dia:object type="Standard - Text" version="1" id="O0">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.13505,-23.1314"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.13505,-23.7264;3.15255,-22.9789"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#Overview#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80000000000000004"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.13505,-23.1314"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O1">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.1367,0.8291"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.1367,0.2341;3.9392,0.9816"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#para_server#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80000000000000004"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.1367,0.8291"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O2">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.225,9.8561"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.213438,9.47704;14.6275,15.9009"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#Incoming connections arrive via TCP at the dispatcher which creates a
-command handler process for each connection.
-
-After the connecting client has been authenticated, the command
-handler propagates the incoming request either to the audio file
-selector (afs) or to the virtual streaming system (vss). Results are sent
-back to the client.
-
-afs maintans the audio file database and  is responsible for selecting
-and loading audio files while vss controls the paraslash senders. When
-vss needs to stream an audio file it requests an open file descriptor from
-afs and feeds small chunks of data (e.g. mp3 frames) to the senders
-which send the chunks to all connected clients.#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.225,9.8561"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O3">
-      <dia:attribute name="obj_pos">
-        <dia:point val="3.1702,14.0975"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="3.1702,13.5025;3.1702,14.25"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>##</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80000000000000004"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="3.1702,14.0975"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:group>
-      <dia:object type="Network - Bus" version="0" id="O4">
-        <dia:attribute name="obj_pos">
-          <dia:point val="6.3534,2.3542"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="3.58996,2.30409;11.989,6.1401"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="6.3534,2.3542"/>
-          <dia:point val="11.9389,2.3663"/>
-        </dia:attribute>
-        <dia:attribute name="line_color">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="bus_handles">
-          <dia:point val="5.21003,3.2968"/>
-          <dia:point val="3.6399,2.3744"/>
-          <dia:point val="7.88141,3.2968"/>
-          <dia:point val="10.175,6.1401"/>
-          <dia:point val="8.92435,3.44756"/>
-          <dia:point val="6.83847,3.44756"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="2" to="O9" connection="11"/>
-          <dia:connection handle="4" to="O10" connection="11"/>
-          <dia:connection handle="5" to="O12" connection="11"/>
-          <dia:connection handle="6" to="O10" connection="3"/>
-          <dia:connection handle="7" to="O10" connection="6"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O5">
-        <dia:attribute name="obj_pos">
-          <dia:point val="5.21006,6.4287"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="5.16003,5.6082;5.26006,6.4787"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="5.21006,6.4287"/>
-          <dia:point val="5.21003,5.6582"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O13" connection="11"/>
-          <dia:connection handle="1" to="O8" connection="1"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - ZigZagLine" version="1" id="O6">
-        <dia:attribute name="obj_pos">
-          <dia:point val="4.0215,3.74907"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="3.4702,3.69907;11.8875,8.4582"/>
-        </dia:attribute>
-        <dia:attribute name="orth_points">
-          <dia:point val="4.0215,3.74907"/>
-          <dia:point val="3.5202,3.74907"/>
-          <dia:point val="3.5202,8.4082"/>
-          <dia:point val="11.8375,8.4082"/>
-          <dia:point val="11.8375,6.88101"/>
-          <dia:point val="11.3172,6.88101"/>
-        </dia:attribute>
-        <dia:attribute name="orth_orient">
-          <dia:enum val="0"/>
-          <dia:enum val="1"/>
-          <dia:enum val="0"/>
-          <dia:enum val="1"/>
-          <dia:enum val="0"/>
-        </dia:attribute>
-        <dia:attribute name="autorouting">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O9" connection="8"/>
-          <dia:connection handle="1" to="O12" connection="5"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - ZigZagLine" version="1" id="O7">
-        <dia:attribute name="obj_pos">
-          <dia:point val="10.175,7.62192"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="5.16006,7.28325;10.225,8.0832"/>
-        </dia:attribute>
-        <dia:attribute name="orth_points">
-          <dia:point val="10.175,7.62192"/>
-          <dia:point val="10.175,8.0332"/>
-          <dia:point val="5.21006,8.0332"/>
-          <dia:point val="5.21006,7.33325"/>
-        </dia:attribute>
-        <dia:attribute name="orth_orient">
-          <dia:enum val="1"/>
-          <dia:enum val="0"/>
-          <dia:enum val="1"/>
-        </dia:attribute>
-        <dia:attribute name="autorouting">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O12" connection="2"/>
-          <dia:connection handle="1" to="O13" connection="2"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Network - Storage" version="1" id="O8">
-        <dia:attribute name="obj_pos">
-          <dia:point val="4.7175,4.4993"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="4.6675,4.4493;5.75257,6.51078"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="4.7175,4.4993"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="0.9850649999999993"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.1588999999999992"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#cccccc"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>##</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.80010001542891407"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="5.21003,6.25828"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O9">
-        <dia:attribute name="obj_pos">
-          <dia:point val="3.86929,3.2968"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="3.81929,3.2468;6.60076,4.25135"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="3.86929,3.2968"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="2.6814705898130646"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.90454545953539145"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#dispatcher#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.52916666975065518"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="5.21003,3.88136"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O10">
-        <dia:attribute name="obj_pos">
-          <dia:point val="6.83847,3.2968"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="6.78847,3.2468;8.97435,4.25135"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="6.83847,3.2968"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="2.0858823545189464"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.90454545953538812"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#senders#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.52916666975065518"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="7.88141,3.88136"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O11">
-        <dia:attribute name="obj_pos">
-          <dia:point val="7.35748,6.4287"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="7.30748,6.3787;8.45572,7.38325"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="7.35748,6.4287"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="1.0482352956954173"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.90454545953538545"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#vss#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.52916666975065518"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="7.8816,7.01326"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O12">
-        <dia:attribute name="obj_pos">
-          <dia:point val="8.8978,6.1401"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="8.8478,6.0901;11.5022,7.67192"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="8.8978,6.1401"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="2.5544117662836525"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.4818181901724667"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#command
-handler#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.52916666975065518"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="10.175,6.74872"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O13">
-        <dia:attribute name="obj_pos">
-          <dia:point val="4.71771,6.4287"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="4.66771,6.3787;5.75242,7.38325"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="4.71771,6.4287"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="0.98470588393071123"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.90454545953538301"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#afs#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.52916666975065518"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="5.21006,7.01326"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O14">
-        <dia:attribute name="obj_pos">
-          <dia:point val="7.8816,6.4287"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="7.83141,4.15135;7.9316,6.4787"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="7.8816,6.4287"/>
-          <dia:point val="7.88141,4.20135"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O11" connection="11"/>
-          <dia:connection handle="1" to="O10" connection="2"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O15">
-        <dia:attribute name="obj_pos">
-          <dia:point val="8.3766,6.88097"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="8.3266,6.83097;9.0828,6.93101"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="8.3766,6.88097"/>
-          <dia:point val="9.0328,6.88101"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O11" connection="5"/>
-          <dia:connection handle="1" to="O12" connection="8"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O16">
-        <dia:attribute name="obj_pos">
-          <dia:point val="5.66933,6.88097"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="5.61933,6.83097;7.4366,6.93097"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="5.66933,6.88097"/>
-          <dia:point val="7.3866,6.88097"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O13" connection="5"/>
-          <dia:connection handle="1" to="O11" connection="8"/>
-        </dia:connections>
-      </dia:object>
-    </dia:group>
-    <dia:object type="Network - An amplifier speaker" version="1" id="O17">
-      <dia:attribute name="obj_pos">
-        <dia:point val="9.8398,-22.1251"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="9.7898,-22.1751;11.2046,-19.4456"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="9.8398,-22.1251"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="1.314752411332281"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="2.6295048226645621"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#ffffff"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Network - General Monitor (With Stand)" version="1" id="O18">
-      <dia:attribute name="obj_pos">
-        <dia:point val="3.20424,-21.8387"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="3.19174,-21.8512;5.40635,-18.76"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="3.20424,-21.8387"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="2.1896140767718091"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="2.1531205088256122"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.05000000074505806"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#ffffff"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>##</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80010001542891407"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="4.29905,-19.0125"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O19">
-      <dia:attribute name="obj_pos">
-        <dia:point val="11.7649,-16.3593"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="11.7144,-16.4098;12.6724,-16.2995"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="11.7649,-16.3593"/>
-        <dia:point val="12.6218,-16.35"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O20" connection="5"/>
-        <dia:connection handle="1" to="O35" connection="2"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="BPMN - Task" version="1" id="O20">
-      <dia:attribute name="obj_pos">
-        <dia:point val="9.08475,-16.8116"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="9.03475,-16.8616;11.9353,-15.8571"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="9.08475,-16.8116"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="2.8005882368718877"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="0.90454545953538812"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#bbe7bb"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#para_server#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="10.485,-16.2359"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="BPMN - Task" version="1" id="O21">
-      <dia:attribute name="obj_pos">
-        <dia:point val="9.03812,-18.5769"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="8.98812,-18.6269;11.9893,-17.6608"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="9.03812,-18.5769"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="2.9011764721660049"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="0.86606061082624863"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#bbe7bb"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#para_audiod#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="10.4887,-18.0204"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="BPMN - Task" version="1" id="O22">
-      <dia:attribute name="obj_pos">
-        <dia:point val="5.75078,-18.5644"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="5.70078,-18.6144;8.66755,-17.6483"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="5.75078,-18.5644"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="2.8667647074601228"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="0.86606061082624919"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#ffffff"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#para_audioc#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="7.18416,-18.0079"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="BPMN - Task" version="1" id="O23">
-      <dia:attribute name="obj_pos">
-        <dia:point val="6.01211,-21.1907"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="5.96211,-21.2407;8.43123,-20.2746"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="6.01211,-21.1907"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="2.369117647058824"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="0.8660606108262402"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#ffffff"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#para_gui#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="7.19667,-20.6342"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="BPMN - Task" version="1" id="O24">
-      <dia:attribute name="obj_pos">
-        <dia:point val="2.98001,-16.8019"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="2.93001,-16.8519;5.65589,-15.8858"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="2.98001,-16.8019"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="2.6258823545189465"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="0.86606061082625097"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#ffffff"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#para_client#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="4.29295,-16.2454"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O25">
-      <dia:attribute name="obj_pos">
-        <dia:point val="6.01211,-20.7577"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="5.35471,-20.8094;6.06224,-20.7076"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="6.01211,-20.7577"/>
-        <dia:point val="5.40484,-20.7593"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O23" connection="8"/>
-        <dia:connection handle="1" to="O18" connection="1"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O26">
-      <dia:attribute name="obj_pos">
-        <dia:point val="8.49446,-18.1314"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="8.44354,-18.1948;9.21742,-18.0805"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="8.49446,-18.1314"/>
-        <dia:point val="9.1665,-18.1439"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O22" connection="5"/>
-        <dia:connection handle="1" to="O21" connection="8"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O27">
-      <dia:attribute name="obj_pos">
-        <dia:point val="7.18416,-18.5644"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="7.13381,-20.3751;7.24702,-18.514"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="7.18416,-18.5644"/>
-        <dia:point val="7.19667,-20.3247"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O22" connection="11"/>
-        <dia:connection handle="1" to="O23" connection="11"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O28">
-      <dia:attribute name="obj_pos">
-        <dia:point val="10.4972,-19.4956"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="10.4382,-19.5461;10.5477,-18.5264"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="10.4972,-19.4956"/>
-        <dia:point val="10.4887,-18.5769"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O17" connection="2"/>
-        <dia:connection handle="1" to="O21" connection="11"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O29">
-      <dia:attribute name="obj_pos">
-        <dia:point val="5.47486,-16.3689"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="5.42473,-16.419;9.25532,-16.3092"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="5.47486,-16.3689"/>
-        <dia:point val="9.20519,-16.3593"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_color">
-        <dia:color val="#888888"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O24" connection="5"/>
-        <dia:connection handle="1" to="O20" connection="8"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O30">
-      <dia:attribute name="obj_pos">
-        <dia:point val="10.485,-16.8116"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="10.4348,-17.761;10.5389,-16.7614"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="10.485,-16.8116"/>
-        <dia:point val="10.4887,-17.7108"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_color">
-        <dia:color val="#888888"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O20" connection="11"/>
-        <dia:connection handle="1" to="O21" connection="2"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O31">
-      <dia:attribute name="obj_pos">
-        <dia:point val="1.575,-10.6689"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="1.575,-11.2639;1.575,-10.5164"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>##</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80000000000000004"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="1.575,-10.6689"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O32">
-      <dia:attribute name="obj_pos">
-        <dia:point val="3.6375,10.9686"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="3.6375,10.3736;3.6375,11.1211"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>##</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80000000000000004"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="3.6375,10.9686"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O33">
-      <dia:attribute name="obj_pos">
-        <dia:point val="2.6375,10.6686"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="2.6375,10.0736;2.6375,10.8211"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>##</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80000000000000004"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="2.6375,10.6686"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O34">
-      <dia:attribute name="obj_pos">
-        <dia:point val="4.29295,-16.8019"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="4.24287,-19.7231;4.34745,-16.7518"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="4.29295,-16.8019"/>
-        <dia:point val="4.29737,-19.673"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O24" connection="11"/>
-        <dia:connection handle="1" to="O18" connection="1"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Network - Storage" version="1" id="O35">
-      <dia:attribute name="obj_pos">
-        <dia:point val="12.6719,-16.9342"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="12.6219,-16.9842;13.7267,-15.1866"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="12.6719,-16.9342"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="1.0048306811423449"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="1.1821537425204058"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#cccccc"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>##</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="13.1743,-15.3816"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O36">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.171225,-14.4141"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.159662,-14.7932;14.6787,-7.87542"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#The two main applications of the paraslash suite (shaded green) are
-para_server and para_audiod. Both run in the background usually.
-para_server maintains the audio file database and acts as the streaming
-source, while para_audiod is the streaming client.
-
-The two client programs, para_client and para_audioc communicate
-with para_server and para_audiod, respectively.
-
-para_gui controls para_server/audiod by executing paraslash commands.
-Command output is shown in a curses window. para_gui automatically
-executes para_audioc to obtain the state of para_audiod and para_server
-and the metadata of the current audio file.
-
-Network connections are shaded grey, local connections black.#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.171225,-14.4141"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O37">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.1003,73.0082"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.1003,72.4132;4.0578,73.1607"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#para_audiod#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80000000000000004"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.1003,73.0082"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O38">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.116202,82.0061"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.116202,81.627;14.5828,87.0631"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#The purpose of para_audiod is to download, decode and play an audio
-stream received from para_server. It fetches the para_server status and
-starts a suitable buffer tree (shaded blue) if an audio stream is available.
-
-The buffer tree usually consists of a receiver, any number of filters and
-a writer. The receiver downloads the audio stream from para_server and
-the filters decode or modify the received data. The writer plays the
-decoded stream.
-
-The dispatcher acts on (local) requests from para_audioc, for example to
-dump information about the current audio file.#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.116202,82.0061"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O39">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.27019,32.9375"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.258628,32.5584;13.8202,37.5006"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#The audio file selector (afs) accepts two different kinds of incoming
-connections: A bidirectional pipe shared with para_server is used for
-passing the file descriptor of the current audio file to the server
-process. The local socket is used by command handlers which query
-or update the database.
-
-To add a new file to the database, afs opens the file and locates an
-audio format handler (afh) that recognizes the file. A new database
-entry with metadata obtained from the afh is then added to the
-database.#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.27019,32.9375"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O40">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.11118,24.9782"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.11118,24.3832;7.22118,25.1307"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#The audio file selector#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80000000000000004"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.11118,24.9782"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O41">
-      <dia:attribute name="obj_pos">
-        <dia:point val="6.8012,28.9591"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="5.61907,28.9091;6.85122,29.0096"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="6.8012,28.9591"/>
-        <dia:point val="5.66909,28.9596"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="4"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O50" connection="8"/>
-        <dia:connection handle="1" to="O43" connection="2"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O42">
-      <dia:attribute name="obj_pos">
-        <dia:point val="7.6012,28.5261"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="7.53094,27.1956;9.25521,28.5964"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="7.6012,28.5261"/>
-        <dia:point val="9.18495,27.2659"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O50" connection="10"/>
-        <dia:connection handle="1" to="O47" connection="0"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Network - Storage" version="1" id="O43">
-      <dia:attribute name="obj_pos">
-        <dia:point val="4.61551,28.3687"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="4.00418,28.3187;6.23168,30.1163"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="4.61551,28.3687"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="1.0048306811423449"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="1.1821537425204058"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#cccccc"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#audio files#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="5.11793,29.9213"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O44">
-      <dia:attribute name="obj_pos">
-        <dia:point val="7.2512,28.5261"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="7.19859,26.8922;7.30128,28.5762"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="7.2512,28.5261"/>
-        <dia:point val="7.24867,26.9423"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O50" connection="11"/>
-        <dia:connection handle="1" to="O46" connection="2"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O45">
-      <dia:attribute name="obj_pos">
-        <dia:point val="7.2512,29.3922"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="7.20042,29.3414;7.32552,30.9457"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="7.2512,29.3922"/>
-        <dia:point val="7.27474,30.8949"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O50" connection="2"/>
-        <dia:connection handle="1" to="O49" connection="11"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="BPMN - Task" version="1" id="O46">
-      <dia:attribute name="obj_pos">
-        <dia:point val="5.84838,26.0762"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="5.79838,26.0262;8.69897,26.9923"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="5.84838,26.0762"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="2.8005882368718877"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="0.86606061082624941"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#bbe7bb"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#para_server#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="7.24867,26.6327"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="BPMN - Task" version="1" id="O47">
-      <dia:attribute name="obj_pos">
-        <dia:point val="9.04127,26.3998"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="8.99127,26.3498;10.0786,27.3159"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="9.04127,26.3998"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="0.98735294275424068"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="0.86606061082625274"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#ffffff"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#afh#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="9.53495,26.9563"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Network - Storage" version="1" id="O48">
-      <dia:attribute name="obj_pos">
-        <dia:point val="8.84318,28.3795"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="8.65435,28.3295;10.0368,30.1271"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="8.84318,28.3795"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="1.0048306811423449"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="1.1821537425204058"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#cccccc"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#osl db#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="9.3456,29.9321"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="BPMN - Task" version="1" id="O49">
-      <dia:attribute name="obj_pos">
-        <dia:point val="5.20738,30.8949"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="5.15738,30.8449;9.39209,31.811"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="5.20738,30.8949"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="4.1347058839307111"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="0.86606061082624874"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#ffffff"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#command handler#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="7.27474,31.4514"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="BPMN - Task" version="1" id="O50">
-      <dia:attribute name="obj_pos">
-        <dia:point val="6.78135,28.5261"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="6.73135,28.4761;7.77105,29.4422"/>
-      </dia:attribute>
-      <dia:attribute name="meta">
-        <dia:composite type="dict"/>
-      </dia:attribute>
-      <dia:attribute name="elem_corner">
-        <dia:point val="6.78135,28.5261"/>
-      </dia:attribute>
-      <dia:attribute name="elem_width">
-        <dia:real val="0.93970588393071131"/>
-      </dia:attribute>
-      <dia:attribute name="elem_height">
-        <dia:real val="0.86606061082625274"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:attribute name="line_colour">
-        <dia:color val="#000000"/>
-      </dia:attribute>
-      <dia:attribute name="fill_colour">
-        <dia:color val="#ffffff"/>
-      </dia:attribute>
-      <dia:attribute name="show_background">
-        <dia:boolean val="true"/>
-      </dia:attribute>
-      <dia:attribute name="line_style">
-        <dia:enum val="0"/>
-        <dia:real val="1"/>
-      </dia:attribute>
-      <dia:attribute name="padding">
-        <dia:real val="0.10000000000000001"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#afs#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="7.2512,29.0826"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="1"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="flip_horizontal">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="flip_vertical">
-        <dia:boolean val="false"/>
-      </dia:attribute>
-      <dia:attribute name="subscale">
-        <dia:real val="1"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Line" version="0" id="O51">
-      <dia:attribute name="obj_pos">
-        <dia:point val="7.7012,28.9591"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="7.65087,28.9088;8.844,29.0167"/>
-      </dia:attribute>
-      <dia:attribute name="conn_endpoints">
-        <dia:point val="7.7012,28.9591"/>
-        <dia:point val="8.79367,28.9663"/>
-      </dia:attribute>
-      <dia:attribute name="numcp">
-        <dia:int val="1"/>
-      </dia:attribute>
-      <dia:attribute name="line_width">
-        <dia:real val="0.10000000149011612"/>
-      </dia:attribute>
-      <dia:connections>
-        <dia:connection handle="0" to="O50" connection="5"/>
-        <dia:connection handle="1" to="O48" connection="2"/>
-      </dia:connections>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O52">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.1025,49.0569"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.1025,48.4619;5.9375,49.2094"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#The OSL database#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.80000000000000004"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.1025,49.0569"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:object type="Standard - Text" version="1" id="O53">
-      <dia:attribute name="obj_pos">
-        <dia:point val="0.2206,58.055"/>
-      </dia:attribute>
-      <dia:attribute name="obj_bb">
-        <dia:rectangle val="0.209037,57.6759;14.1806,68.0509"/>
-      </dia:attribute>
-      <dia:attribute name="text">
-        <dia:composite type="text">
-          <dia:attribute name="string">
-            <dia:string>#Metadata about all known audio files is stored in serveral tables of a
-database which is driven by libosl, the object storage layer library.
-
-The "audio files" table is the main table of the database. It contains
-path, hash and metadata of each known file.
-
-The "attributes" table maps each of the 64 possible attributes to a
-string. The attribute value of the file's metadata is translated through
-this table.
-
-The tables shown shaded are blob tables which support add, rm, mv,
-cat, ls commands. All of these are optional.
-
-The "score" table describes the subset of admissible files for the
-current playlist or mood. This table is created on demand, resides
-only in memory and is discarded on exit.
-
-When the next audio file is to be streamed, the audio file selector gets
-the entry with the highest score from the "score" table, obtains path,
-hash, and metadata for this entry from the "audio files" table, opens
-the path and verifies the hash.#</dia:string>
-          </dia:attribute>
-          <dia:attribute name="font">
-            <dia:font family="sans" style="0" name="Helvetica"/>
-          </dia:attribute>
-          <dia:attribute name="height">
-            <dia:real val="0.49388889176727813"/>
-          </dia:attribute>
-          <dia:attribute name="pos">
-            <dia:point val="0.2206,58.055"/>
-          </dia:attribute>
-          <dia:attribute name="color">
-            <dia:color val="#000000"/>
-          </dia:attribute>
-          <dia:attribute name="alignment">
-            <dia:enum val="0"/>
-          </dia:attribute>
-        </dia:composite>
-      </dia:attribute>
-      <dia:attribute name="valign">
-        <dia:enum val="3"/>
-      </dia:attribute>
-    </dia:object>
-    <dia:group>
-      <dia:object type="Flowchart - Extract" version="1" id="O54">
-        <dia:attribute name="obj_pos">
-          <dia:point val="2.048,53.4404"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="1.88598,53.3799;6.86502,55.0782"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="2.048,53.4404"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="4.6550000029802332"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.5877777865147884"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#audio files#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="4.3755,54.7547"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Flowchart - Extract" version="1" id="O55">
-        <dia:attribute name="obj_pos">
-          <dia:point val="9.0846,55.0212"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="8.94554,54.9564;13.0687,56.659"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="9.0846,55.0212"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="3.8450000029802323"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.5877777865147775"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#cccccc"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#playlists#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="11.0071,56.3355"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Flowchart - Extract" version="1" id="O56">
-        <dia:attribute name="obj_pos">
-          <dia:point val="5.9317,52.7104"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="5.803,52.6426;9.5304,54.3482"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="5.9317,52.7104"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="3.4700000029802327"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.5877777865147873"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#cccccc"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#images#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="7.6667,54.0247"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Flowchart - Extract" version="1" id="O57">
-        <dia:attribute name="obj_pos">
-          <dia:point val="6.8382,50.5875"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="6.68191,50.5261;11.4495,52.2253"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="6.8382,50.5875"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="4.4550000029802321"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.587777786514786"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#attributes#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="9.0657,51.9018"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Flowchart - Extract" version="1" id="O58">
-        <dia:attribute name="obj_pos">
-          <dia:point val="9.7265,52.7977"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="9.61888,52.7202;12.5141,54.4355"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="9.7265,52.7977"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="2.6800000029802327"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.5877777865147884"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#cccccc"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#lyrics#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="11.0665,54.112"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Flowchart - Extract" version="1" id="O59">
-        <dia:attribute name="obj_pos">
-          <dia:point val="5.4821,55.0581"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="5.35938,54.9882;8.85482,56.6959"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="5.4821,55.0581"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="3.2500000029802325"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.58777778651479"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#cccccc"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#moods#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="7.1071,56.3724"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Flowchart - Extract" version="1" id="O60">
-        <dia:attribute name="obj_pos">
-          <dia:point val="2.4988,50.5557"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="2.35946,50.4909;6.49314,52.1935"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="2.4988,50.5557"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="3.855000002980232"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.587777786514786"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="4"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#score#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="4.4263,51.87"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O61">
-        <dia:attribute name="obj_pos">
-          <dia:point val="4.41144,52.1937"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="4.33898,52.1428;4.46231,53.4702"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="4.41144,52.1937"/>
-          <dia:point val="4.38985,53.4194"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O60" connection="12"/>
-          <dia:connection handle="1" to="O54" connection="12"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O62">
-        <dia:attribute name="obj_pos">
-          <dia:point val="5.03792,53.8314"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="4.96922,52.1555;7.74887,53.9001"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="5.03792,53.8314"/>
-          <dia:point val="7.68017,52.2242"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O54" connection="12"/>
-          <dia:connection handle="1" to="O57" connection="12"/>
-        </dia:connections>
-      </dia:object>
-    </dia:group>
-    <dia:group>
-      <dia:object type="BPMN - Task" version="1" id="O63">
-        <dia:attribute name="obj_pos">
-          <dia:point val="4.42569,77.8748"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="4.37569,77.8248;7.15716,78.8293"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="4.42569,77.8748"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="2.6814705898130642"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.90454545953538923"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#dispatcher#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.52916666975065518"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="5.76643,78.4594"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O64">
-        <dia:attribute name="obj_pos">
-          <dia:point val="1.63592,76.2823"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="1.58592,76.2323;5.11916,77.2368"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="1.63592,76.2823"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="3.4332352956954173"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.9045454595353889"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#status fetcher#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.52916666975065518"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="3.35254,76.8669"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Network - An amplifier speaker" version="1" id="O65">
-        <dia:attribute name="obj_pos">
-          <dia:point val="11.9716,75.7366"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="11.9216,75.6866;13.0196,77.7826"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="11.9716,75.7366"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="0.99802008040072598"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="1.996040160801452"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#ffffff"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O66">
-        <dia:attribute name="obj_pos">
-          <dia:point val="4.31303,74.415"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="4.26303,74.365;7.16362,75.3311"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="4.31303,74.415"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="2.8005882368718877"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.8660606108262453"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#bbe7bb"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#para_server#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="5.71332,74.9715"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O67">
-        <dia:attribute name="obj_pos">
-          <dia:point val="7.11064,76.3016"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="7.06064,76.2516;9.18034,77.2177"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="7.11064,76.3016"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="2.0197058839307109"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.86606061082625108"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#bbbbee"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#receiver#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="8.12049,76.8581"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O68">
-        <dia:attribute name="obj_pos">
-          <dia:point val="9.56352,76.3016"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="9.51352,76.2516;11.1726,77.2177"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="9.56352,76.3016"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="1.5591176486365934"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.86606061082625141"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#bbbbee"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#filter1#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="10.3431,76.8581"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O69">
-        <dia:attribute name="obj_pos">
-          <dia:point val="9.48684,78.4014"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="9.43684,78.3514;11.2283,79.3175"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="9.48684,78.4014"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="1.691470589813064"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.86606061082625141"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#bbbbee"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#filter 2#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="10.3326,78.9579"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O70">
-        <dia:attribute name="obj_pos">
-          <dia:point val="11.6997,78.4014"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="11.6497,78.3514;13.2903,79.3175"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="11.6997,78.4014"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="1.5405882368718877"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.86606061082624797"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#bbbbee"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#writer#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="12.47,78.9579"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="BPMN - Task" version="1" id="O71">
-        <dia:attribute name="obj_pos">
-          <dia:point val="4.32709,79.7644"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="4.27709,79.7144;7.24386,80.6805"/>
-        </dia:attribute>
-        <dia:attribute name="meta">
-          <dia:composite type="dict"/>
-        </dia:attribute>
-        <dia:attribute name="elem_corner">
-          <dia:point val="4.32709,79.7644"/>
-        </dia:attribute>
-        <dia:attribute name="elem_width">
-          <dia:real val="2.8667647074601228"/>
-        </dia:attribute>
-        <dia:attribute name="elem_height">
-          <dia:real val="0.86606061082624586"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:attribute name="line_colour">
-          <dia:color val="#000000"/>
-        </dia:attribute>
-        <dia:attribute name="fill_colour">
-          <dia:color val="#888888"/>
-        </dia:attribute>
-        <dia:attribute name="show_background">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_style">
-          <dia:enum val="0"/>
-          <dia:real val="1"/>
-        </dia:attribute>
-        <dia:attribute name="padding">
-          <dia:real val="0.10000000000000001"/>
-        </dia:attribute>
-        <dia:attribute name="text">
-          <dia:composite type="text">
-            <dia:attribute name="string">
-              <dia:string>#para_audioc#</dia:string>
-            </dia:attribute>
-            <dia:attribute name="font">
-              <dia:font family="sans" style="0" name="Helvetica"/>
-            </dia:attribute>
-            <dia:attribute name="height">
-              <dia:real val="0.49388889176727813"/>
-            </dia:attribute>
-            <dia:attribute name="pos">
-              <dia:point val="5.76047,80.3209"/>
-            </dia:attribute>
-            <dia:attribute name="color">
-              <dia:color val="#000000"/>
-            </dia:attribute>
-            <dia:attribute name="alignment">
-              <dia:enum val="1"/>
-            </dia:attribute>
-          </dia:composite>
-        </dia:attribute>
-        <dia:attribute name="flip_horizontal">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="flip_vertical">
-          <dia:boolean val="false"/>
-        </dia:attribute>
-        <dia:attribute name="subscale">
-          <dia:real val="1"/>
-        </dia:attribute>
-      </dia:object>
-      <dia:object type="Standard - ZigZagLine" version="1" id="O72">
-        <dia:attribute name="obj_pos">
-          <dia:point val="7.11362,74.848"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="7.06362,74.798;8.17049,76.3516"/>
-        </dia:attribute>
-        <dia:attribute name="orth_points">
-          <dia:point val="7.11362,74.848"/>
-          <dia:point val="8.12049,74.848"/>
-          <dia:point val="8.12049,76.3016"/>
-        </dia:attribute>
-        <dia:attribute name="orth_orient">
-          <dia:enum val="0"/>
-          <dia:enum val="1"/>
-        </dia:attribute>
-        <dia:attribute name="autorouting">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_color">
-          <dia:color val="#888888"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O66" connection="5"/>
-          <dia:connection handle="1" to="O67" connection="11"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - ZigZagLine" version="1" id="O73">
-        <dia:attribute name="obj_pos">
-          <dia:point val="4.31303,74.848"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="3.26303,73.798;4.36303,76.3323"/>
-        </dia:attribute>
-        <dia:attribute name="orth_points">
-          <dia:point val="4.31303,74.848"/>
-          <dia:point val="3.31303,74.848"/>
-          <dia:point val="3.31303,73.848"/>
-          <dia:point val="3.35254,73.848"/>
-          <dia:point val="3.35254,76.2823"/>
-        </dia:attribute>
-        <dia:attribute name="orth_orient">
-          <dia:enum val="0"/>
-          <dia:enum val="1"/>
-          <dia:enum val="0"/>
-          <dia:enum val="1"/>
-        </dia:attribute>
-        <dia:attribute name="autorouting">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_color">
-          <dia:color val="#888888"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O66" connection="8"/>
-          <dia:connection handle="1" to="O64" connection="11"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - ZigZagLine" version="1" id="O74">
-        <dia:attribute name="obj_pos">
-          <dia:point val="3.35254,77.1868"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="3.30254,77.1368;4.47569,78.3771"/>
-        </dia:attribute>
-        <dia:attribute name="orth_points">
-          <dia:point val="3.35254,77.1868"/>
-          <dia:point val="3.35254,78.3271"/>
-          <dia:point val="4.42569,78.3271"/>
-        </dia:attribute>
-        <dia:attribute name="orth_orient">
-          <dia:enum val="1"/>
-          <dia:enum val="0"/>
-        </dia:attribute>
-        <dia:attribute name="autorouting">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O64" connection="2"/>
-          <dia:connection handle="1" to="O63" connection="8"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - ZigZagLine" version="1" id="O75">
-        <dia:attribute name="obj_pos">
-          <dia:point val="7.10716,78.3271"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="7.05716,77.1177;8.17049,78.3771"/>
-        </dia:attribute>
-        <dia:attribute name="orth_points">
-          <dia:point val="7.10716,78.3271"/>
-          <dia:point val="8.12049,78.3271"/>
-          <dia:point val="8.12049,77.1677"/>
-        </dia:attribute>
-        <dia:attribute name="orth_orient">
-          <dia:enum val="0"/>
-          <dia:enum val="1"/>
-        </dia:attribute>
-        <dia:attribute name="autorouting">
-          <dia:boolean val="true"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O63" connection="5"/>
-          <dia:connection handle="1" to="O67" connection="2"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O76">
-        <dia:attribute name="obj_pos">
-          <dia:point val="5.76643,78.7793"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="5.71017,78.729;5.81673,79.8147"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="5.76643,78.7793"/>
-          <dia:point val="5.76047,79.7644"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O63" connection="2"/>
-          <dia:connection handle="1" to="O71" connection="11"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O77">
-        <dia:attribute name="obj_pos">
-          <dia:point val="9.02049,76.7346"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="8.97049,76.6846;9.71808,76.7846"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="9.02049,76.7346"/>
-          <dia:point val="9.66808,76.7346"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O67" connection="5"/>
-          <dia:connection handle="1" to="O68" connection="8"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O78">
-        <dia:attribute name="obj_pos">
-          <dia:point val="10.3431,77.1677"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="10.2822,77.1173;10.3935,78.4518"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="10.3431,77.1677"/>
-          <dia:point val="10.3326,78.4014"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O68" connection="2"/>
-          <dia:connection handle="1" to="O69" connection="11"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O79">
-        <dia:attribute name="obj_pos">
-          <dia:point val="12.4706,77.7326"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="12.42,77.6826;12.5206,78.4514"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="12.4706,77.7326"/>
-          <dia:point val="12.47,78.4014"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O65" connection="2"/>
-          <dia:connection handle="1" to="O70" connection="11"/>
-        </dia:connections>
-      </dia:object>
-      <dia:object type="Standard - Line" version="0" id="O80">
-        <dia:attribute name="obj_pos">
-          <dia:point val="11.0658,78.8344"/>
-        </dia:attribute>
-        <dia:attribute name="obj_bb">
-          <dia:rectangle val="11.0158,78.7844;11.849,78.8844"/>
-        </dia:attribute>
-        <dia:attribute name="conn_endpoints">
-          <dia:point val="11.0658,78.8344"/>
-          <dia:point val="11.799,78.8344"/>
-        </dia:attribute>
-        <dia:attribute name="numcp">
-          <dia:int val="1"/>
-        </dia:attribute>
-        <dia:attribute name="line_width">
-          <dia:real val="0.10000000149011612"/>
-        </dia:attribute>
-        <dia:connections>
-          <dia:connection handle="0" to="O69" connection="5"/>
-          <dia:connection handle="1" to="O70" connection="8"/>
-        </dia:connections>
-      </dia:object>
-    </dia:group>
-  </dia:layer>
-</dia:diagram>
index b226943b199a224e6b38e4685235707982e23f11..d6d690a16bd5299a6fdb8d304cfe48fc13dbc6c3 100644 (file)
@@ -5,12 +5,6 @@
 <h2> General information </h2>
 
 <ul>
-       <li> <a href="overview.pdf">overview.pdf</a>,
-
-               a pdf file containing a sketch which illustrates how
-               the pieces of paraslash work together.
-
-       </li>
        <li> <a href="manual.html">user manual</a>,
                Installation, Configuration and Usage.
        </li>
@@ -29,7 +23,7 @@
        [<a href="para_filter.man.html">para_filter</a>]
        [<a href="para_write.man.html">para_write</a>]
        [<a href="para_gui.man.html">para_gui</a>]
-       [<a href="para_fade.man.html">para_fade</a>]
+       [<a href="para_mixer.man.html">para_mixere</a>]
        [<a href="para_play.man.html">para_play</a>]
 </p>
 
index a09fd1c868f5c9aa1eabd9b004d00dc30754d256..9ef92b7af654ec06f193ad5dd3941648b82a83b8 100644 (file)
@@ -9,14 +9,14 @@ provided at this point. There are several ways to download the source:
 
                Clone the git repository by executing
 
-               <p> <pre> <kbd>
+               <pre> <kbd>
                        git clone git://git.tuebingen.mpg.de/paraslash.git
-               </kbd> </pre> </p>
+               </kbd> </pre>
 
                <p> The repository contains the full history of the
                project since 2006, all work in progress and the source
-               code for the web pages. Choosing this option allows to
-               check out any of the four integration branches maint,
+               code for the web pages. Choosing this option allows the
+               checkout of any of the four integration branches maint,
                master, next, pu (see the
 
                <a href="manual.html#Git.branches">Git branches</a>
@@ -52,7 +52,7 @@ provided at this point. There are several ways to download the source:
 
                Whenever significant changes are incorporated a
 
-               <a href="releases/paraslash-git.tar.bz2">tarball</a>
+               <a href="releases/paraslash-git.tar.xz">tarball</a>
 
                of the current master branch is created. All changes in
                this tarball will be included in the next release. Like
@@ -68,7 +68,7 @@ provided at this point. There are several ways to download the source:
                        <a href="http://git.tuebingen.mpg.de/paraslash.git">gitweb</a>
 
                page contains a snapshot link for each revision. This
-               allows to get a specific revision without downloading
+               allows getting a specific revision without downloading
                the full history.
 
        </li>
index fd17a649265020637a64e568160f83098c42dafb..06ff03362e8a33dfe2b02bd2c214892dd3ba59c4 100644 (file)
Binary files a/web/images/paraslash.ico and b/web/images/paraslash.ico differ
index a06d6a1a9d67f4d4bf62fc81fcee3348c0d9993b..804752cf5b6568cbb687c784fede125f3d1d24ab 100644 (file)
Binary files a/web/images/paraslash.png and b/web/images/paraslash.png differ
index 12454ee2ae55ba131165ae2d442041879dad33b1..db28a699bdd05516d17de4bf837cc110f737df7e 100644 (file)
@@ -12,81 +12,143 @@ paraslash executable.
 Introduction
 ============
 
-In this chapter we give an [overview](#Overview) of the interactions of
-the two main programs contained in the paraslash package, followed by
+In this chapter we give an [overview](#Overview) of the interactions
+of the programs contained in the paraslash package, followed by
 [brief descriptions](#The.paraslash.executables) of all executables.
 
 Overview
 --------
 
 The core functionality of the para suite is provided by two main
-executables, para_server and para_audiod. The former maintains a
-database of audio files and streams these files to para_audiod which
-receives and plays the stream.
-
-In a typical setting, both para_server and para_audiod act as
-background daemons whose functionality is controlled by client
-programs: the para_audioc client controls para_audiod over a local
-socket while the para_client program connects to para_server over a
-local or remote networking connection.
-
-Typically, these two daemons run on different hosts but a local setup
-is also possible.
+applications, para_server and para_audiod. para_server maintains
+the audio file database and acts as the streaming source, while
+para_audiod is the streaming client. Usually, both run in the
+background on different hosts but a local setup is also possible.
 
 A simplified picture of a typical setup is as follows
 
-       server_host                                  client_host
-       ~~~~~~~~~~~                                  ~~~~~~~~~~~
 
-       +-----------+         audio stream           +-----------+
-       |para_server| -----------------------------> |para_audiod|
-       +-----------+                                +-----------+
-            ^                                            ^
-            |                                            |
-            |                                            | connect
-            |                                            |
-            |                                            |
-            |                                       +-----------+
-            |                                       |para_audioc|
-            |                                       +-----------+
-            |
-            |
-            |                  connect              +-----------+
-            +-------------------------------------- |para_client|
-                                                    +-----------+
+                                               .____________________.
+                                               |       ______       |
+       .-----------------------.               |    .d########b.    |
+       |.---------------------.|               |  .d############b   |
+       ||                     ||               | .d######""####//b. |
+       ||                     ||               | 9######(  )######P |
+       ||                     ||               | 'b######++######d' |
+       ||       Screen        ||               |  "9############P"  |
+       ||                     ||               |   "9a########P"    |
+       ||                     ||               |      `""""''       |
+       |`---------------------'|               |  ________________  |
+       `-----------------------'               | |________________| |
+             ___)     (___                     |____________________|
+             `-._______.-'                           loudspeaker
+                   |                                     |
+                   |                                     |
+                   |                                     |
+             .____/ \___.     ._____________.     ._____/ \_____.
+             |          |     |             |     |             |
+             | para_gui |-----| para_audioc |-----| para_audiod |
+             |____   ___|     |_____________|     |_____   _____|
+                  \ /                                   \ /
+                   |                                     |
+                   |                                     |
+                   |                                     |
+            ._____/ \_____.                       ._____/ \_____.
+            |             |                       |             |
+            | para_client |-----------------------| para_server |
+            |_____________|                       |_____   _____|
+                                                        \ /
+                                                         |
+                                                         |
+                                                     .-'"""`-.
+                                                    (         )
+                                                    |`-.___.-'|
+                                                    |         |
+                                                    |. ' " ` .|
+                                                    |         |
+                                                     `-.___.-'
+                                                      Database
+
+The two client programs, para_client and para_audioc communicate with
+para_server and para_audiod, respectively.
+
+para_gui controls para_server and para_audiod by executing para_client
+and para_audioc. In particular, it runs a command to obtain the state
+of para_audiod and para_server, and the metadata of the current audio
+file. This information is pretty-printed in a curses window.
+
 The paraslash executables
 -------------------------
 
-### para_server ###
+<h3> para_server </h3>
 
 para_server streams binary audio data (MP3, ...) over local and/or
 remote networks. It listens on a TCP port and accepts commands such
-as play, stop, pause, next from authenticated clients. There are
-many more commands though, see the man page of para_server for a
-description of all commands.
-
-It supports three built-in network streaming protocols
-(senders/receivers): HTTP, DCCP, or UDP. This is explained in more
-detail in the section on [networking](#Networking).
-
-The built-in audio file selector of paraslash is used to manage your
-audio files. It maintains statistics on the usage of all available
-audio files such as last-played time, and the number of times each
-file was selected.
-
-Additional information may be added to the database to allow
+as play, stop, pause, next from authenticated clients. The components
+of para_server are illustrated in the following diagram:
+
+       ______________________________________________________________________ network
+             |                                  |      |      |         |
+             |           .-'""""`-.             |      |      |         |
+             |          (          )            |      |      |         |
+       .____/ \_____.   |`-.____.-'|      .____/ \____/ \____/ \____.   |
+       |            |   |          |      |                         |   |
+       | dispatcher |   | database |      | senders (http/udp/dccp) |   |
+       |____   _____|   |          |      |___________   ___________|   |
+            \ /         |. ' "" ` .|                  \ /               |
+             |          |          |                   |                |
+             |           `-.____.-'                    |                |
+             |               |                         |                |
+             |               |                         |                |
+             |               |                         |                |
+             |        ._____/ \_____.        .________/ \________.      |
+             |        |             |        |                   |      |
+             |        | audio  file |________| virtual streaming |      |
+             |        |  selector   |        |      system       |      |
+             |        |_____   _____|        |________   ________|      |
+             |              \ /                       \ /               |
+             |               |                         |                |
+             |               |                         |                |
+             |               |   ._________________.   |                |
+             |               |   |                 |   |                |
+             |               `---| command handler |---'                |
+             |                   |____   ___   ____|                    |
+             |                        \ /   \ /                         |
+             |                         |     |                          |
+             |                         |     |                          |
+             |                         |     |                          |
+             `-------------------------'     `--------------------------'
+
+
+Incoming connections arrive at the dispatcher which creates a process
+dedicated to the connection. Its task is to authenticate the client
+and to run the command handler which forwards the client request to
+either the audio file selector or the virtual streaming system. Results
+(if any) are sent back to the client.
+
+The audio file selector manages audio files using various database
+tables. It maintains statistics on the usage of all audio files such as
+last-played time and the number of times each file was selected. It
+is also responsible for selecting and loading audio files for
+streaming. Additional information may be added to the database to allow
 fine-grained selection based on various properties of the audio file,
-including information found in (ID3) tags. However, old-fashioned
-playlists are also supported.
-
-It is also possible to store images (album covers) and lyrics in the
-database and associate these to the corresponding audio files.
-
+including information found in (ID3) tags. Simple playlists are also
+supported. It is possible to store images (album covers) and lyrics
+in the database and associate these to the corresponding audio files.
 The section on the [audio file selector](#The.audio.file.selector)
-discusses this topic.
+discusses this topic in more detail.
+
+Another component of para_server is the virtual streaming system,
+which controls the paraslash senders. During streaming it requests
+small chunks of data (e.g., mp3 frames) from the audio file selector
+and feeds them to the senders which forward the chunks to connected
+clients.
 
+The three senders of para_server correspond to network streaming
+protocols based on HTTP, DCCP, or UDP. This is explained in the
+section on [networking](#Networking).
 
-### para_client ###
+<h3> para_client </h3>
 
 The client program to connect to para_server. paraslash commands
 are sent to para_server and the response is dumped to STDOUT. This
@@ -101,40 +163,78 @@ If para_client is started without non-option arguments, an interactive
 session (shell) is started. Command history and command completion are
 supported through libreadline.
 
-### para_audiod ###
-
-The local daemon that collects information from para_server.
-
-It runs on the client side and connects to para_server. As soon as
-para_server announces the availability of an audio stream, para_audiod
-starts an appropriate receiver, any number of filters and a paraslash
-writer to play the stream.
-
-Moreover, para_audiod listens on a local socket and sends status
-information about para_server and para_audiod to local clients on
-request. Access via this local socket may be restricted by using Unix
-socket credentials, if available.
-
-
-### para_audioc ###
+<h3> para_audiod </h3>
+
+The purpose of para_audiod is to download, decode and play an audio
+stream received from para_server. A typical setup looks as follows.
+
+
+              .----------------------------.
+              |                            |
+              |                            |
+       ._____/ \_____.                .___/ \____.
+       |             |     .----------|          |
+       | para_server |     |   .______| receiver |
+       |_____    ____|     |   |      |___   ____|
+             \ /           |   |          \ /
+              |            |   |           |
+              |            |   |           |
+              |            |   |           |
+       ._____/ \_____.     |   |      .___/ \____.
+       |             |     |   |      |          |
+       | status task |-----+   |      | filter 1 |
+       |_____________|         |      |___   ____|
+                               |          \ /
+                               |           |            .____________________.
+                               |           |            |       ______       |
+       .____________.          |      .___/ \____.      |    .d########b.    |
+       |            |          |      |          |      |  .d############b   |
+       | dispatcher |----------'      | filter 2 |      | .d######""####//b. |
+       |_____   ____|                 |___   ____|      | 9######(  )######P |
+             \ /                          \ /           | 'b######++######d' |
+              |                            |            |  "9############P"  |
+              |                            |            |   "9a########P"    |
+       ._____/ \_____.                .___/ \____.      |      `""""''       |
+       |             |                |          |      |  ________________  |
+       | para_audioc |                |  writer  |------| |________________| |
+       |_____________|                |__________|      |____________________|
+
+
+The status task of para_audiod connects to para_server and runs the
+"stat" command to retrieve the current server status. If an audio
+stream is available, para_audiod starts a so-called buffer tree to
+play the stream.
+
+The buffer tree consists of a receiver, any number of filters and a
+writer. The receiver downloads the audio stream from para_server and
+the filters decode or modify the received data. The writer plays the
+decoded stream.
+
+The dispatcher of para_audiod listens on a local socket and runs
+audiod commands on behalf of para_audioc. For example, para_gui runs
+para_audioc to obtain status information about para_audiod and the
+current audio file. Access to the local socket may be restricted by
+means of Unix socket credentials.
+
+<h3> para_audioc </h3>
 
 The client program which talks to para_audiod. Used to control
 para_audiod, to receive status info, or to grab the stream at any
 point of the decoding process. Like para_client, para_audioc supports
 interactive sessions on systems with libreadline.
 
-### para_recv ###
+<h3> para_recv </h3>
 
 A command line HTTP/DCCP/UDP stream grabber. The http mode is
 compatible with arbitrary HTTP streaming sources (e.g. icecast).
 In addition to the three network streaming modes, para_recv can also
 operate in local (afh) mode. In this mode it writes the content of
 an audio file on the local file system in complete chunks to stdout,
-optionally 'just in time'. This allows to cut an audio file without
-first decoding it, and it enables third-party software which is unaware
-of the particular audio format to send complete frames in real time.
+optionally 'just in time'. This allows cutting audio files without
+decoding, and it enables third-party software which is unaware of
+the particular audio format to send complete frames in real time.
 
-### para_filter ###
+<h3> para_filter </h3>
 
 A filter program that reads from STDIN and writes to STDOUT.
 Like para_recv, this is an atomic building block which can be used to
@@ -143,31 +243,33 @@ different functionalities in one tool: decoders for multiple audio
 formats and a number of processing filters, among these a normalizer
 for audio volume.
 
-### para_afh ###
+<h3> para_afh </h3>
 
 A small stand-alone program that prints tech info about the given
 audio file to STDOUT. It can be instructed to print a "chunk table",
 an array of offsets within the audio file.
 
-### para_write ###
+<h3> para_write </h3>
 
 A modular audio stream writer. It supports a simple file writer
-output plug-in and optional WAV/raw players for ALSA (Linux) and for
-coreaudio (Mac OS). para_write can also be used as a stand-alone WAV
-or raw audio player.
+output plug-in and optional WAV/raw players for ALSA (Linux) and OSS.
+para_write can also be used as a stand-alone WAV or raw audio player.
 
-### para_play ###
+<h3> para_play </h3>
 
-A command line audio player.
+A command line audio player which supports the same audio formats as
+para_server. It differs from other players in that it has an insert
+and a command mode, like the vi editor. Line editing is based on
+libreadline, and tab completion and command history are supported.
 
-### para_gui ###
+<h3> para_gui </h3>
 
 Curses-based gui that presents status information obtained in a curses
 window. Appearance can be customized via themes. para_gui provides
 key-bindings for the most common server commands and new key-bindings
 can be added easily.
 
-### para_fade ###
+<h3> para_mixer </h3>
 
 An alarm clock and volume-fader for OSS and ALSA.
 
@@ -183,25 +285,28 @@ source code and the steps that have to be performed in order to
 
 Requirements
 ------------
-### For the impatient ###
+<h3> For the impatient </h3>
 
+       git clone git://git.tuebingen.mpg.de/lopsub
+       cd lopsub && make && sudo make install
        git clone git://git.tuebingen.mpg.de/osl
        cd osl && make && sudo make install && sudo ldconfig
-       sudo apt-get install autoconf libssl-dev help2man gengetopt m4 \
+       sudo apt-get install autoconf libssl-dev m4 \
               libmad0-dev libid3tag0-dev libasound2-dev libvorbis-dev \
-              libfaad-dev libspeex-dev libFLAC-dev libsamplerate-dev realpath \
+              libfaad-dev libspeex-dev libflac-dev libsamplerate-dev \
               libasound2-dev libao-dev libreadline-dev libncurses-dev \
               libopus-dev
 
-### Detailed description ###
+<h3> Detailed description </h3>
 
 In any case you will need
 
-- [libosl](http://people.tuebingen.mpg.de/maan/osl/). The _object
-storage layer_ library is used by para_server. To clone the source
-code repository, execute
+- [lopsub](http://people.tuebingen.mpg.de/maan/lopsub/). The long
+option parser for subcommands generates the command line and config
+file parsers for all paraslash executables. Clone the source code
+repository with
 
-               git clone git://git.tuebingen.mpg.de/osl
+               git clone git://git.tuebingen.mpg.de/lopsub
 
 - [gcc](ftp://ftp.gnu.org/pub/gnu/gcc) or
 [clang](http://clang.llvm.org). All gcc versions >= 4.2 are currently
@@ -214,19 +319,18 @@ disto. On BSD systems the gnu make executable is often called gmake.
 during compilation require the _Bourne again shell_.  It is most
 likely already installed.
 
-- [gengetopt](ftp://ftp.gnu.org/pub/gnu/gengetopt/) is needed to
-generate the C code for the command line parsers of all paraslash
-executables.
-
-- [help2man](ftp://ftp.gnu.org/pub/gnu/help2man) is used to create
-the man pages.
-
 - [m4](ftp://ftp.gnu.org/pub/gnu/m4/). Some source files are generated
 from templates by the m4 macro processor.
 
 Optional:
 
-- [openssl](http://www.openssl.org/) or
+- [libosl](http://people.tuebingen.mpg.de/maan/osl/). The _object
+storage layer_ library is used by para_server. To clone the source
+code repository, execute
+
+               git clone git://git.tuebingen.mpg.de/osl
+
+- [openssl](https://www.openssl.org/) or
 [libgcrypt](ftp://ftp.gnupg.org/gcrypt/libgcrypt/).  At least one
 of these two libraries is needed as the backend for cryptographic
 routines on both the server and the client side. Both openssl and
@@ -234,28 +338,36 @@ libgcrypt are usually shipped with the distro, but you might have
 to install the development package (`libssl-dev` or `libgcrypt-dev`
 on debian systems) as well.
 
+- [flex](https://github.com/westes/flex) and
+[bison](https://www.gnu.org/software/bison) are needed to build the
+mood parser of para_server. The build system will skip para_server
+if these tools are not installed.
+
 - [libmad](http://www.underbit.com/products/mad/). To compile in MP3
 support for paraslash, the development package must be installed. It
 is called `libmad0-dev` on debian-based systems. Note that libmad is
 not necessary on the server side, i.e., for sending MP3 files.
 
 - [libid3tag](http://www.underbit.com/products/mad/). For version-2
-ID3 tag support, you willl need the libid3tag development package
+ID3 tag support, you will need the libid3tag development package
 `libid3tag0-dev`. Without libid3tag, only version-1 tags are
 recognized. The mp3 tagger also needs this library for modifying
 (id3v1 and id3v2) tags.
 
-- [ogg vorbis](http://www.xiph.org/downloads/). For ogg vorbis streams
+- [ogg vorbis](https://www.xiph.org/downloads/). For ogg vorbis streams
 you need libogg, libvorbis, libvorbisfile. The corresponding Debian
 packages are called `libogg-dev` and `libvorbis-dev`.
 
-- [libfaad](http://www.audiocoding.com/). For aac files (m4a) you
-need libfaad (`libfaad-dev`).
+- [libfaad and mp4ff](https://sourceforge.net/projects/faac/). For aac files
+(m4a) you need libfaad and libmp4ff (package: `libfaad-dev`). Note
+that for some distributions, e.g. Ubuntu, mp4ff is not part of the
+libfaad package. Install the faad library from sources (available
+through the above link) to get the mp4ff library and header files.
 
-- [speex](http://www.speex.org/). In order to stream or decode speex
+- [speex](https://www.speex.org/). In order to stream or decode speex
 files, libspeex (`libspeex-dev`) is required.
 
-- [flac](http://flac.sourceforge.net/). To stream or decode files
+- [flac](https://xiph.org/flac/). To stream or decode files
 encoded with the _Free Lossless Audio Codec_, libFLAC (`libFLAC-dev`)
 must be installed.
 
@@ -266,7 +378,7 @@ installed. Debian package: `libsamplerate-dev`.
 - [alsa-lib](ftp://ftp.alsa-project.org/pub/lib/). On Linux, you will
 need to have the ALSA development package `libasound2-dev` installed.
 
-- [libao](http://downloads.xiph.org/releases/ao/). Needed to build
+- [libao](https://ftp.osuosl.org/pub/xiph/releases/ao/). Needed to build
 the ao writer (ESD, PulseAudio,...).  Debian package: `libao-dev`.
 
 - [curses](ftp://ftp.gnu.org/pub/gnu/ncurses). Needed for
@@ -309,7 +421,7 @@ to install executables under /usr/local/bin and the man pages under
 Configuration
 -------------
 
-### Create a paraslash user ###
+<h3> Create a paraslash user </h3>
 
 In order to control para_server at runtime you must create a paraslash
 user. As authentication is based on the RSA crypto system you'll have
@@ -334,7 +446,7 @@ following commands:
 Next, change to the "bar" account on client_host and generate the
 key pair with the commands
 
-       ssh-keygen -q -t rsa -b 2048 -N '' -f $key
+       ssh-keygen -q -t rsa -b 2048 -N '' -m RFC4716
 
 This generates the two files id_rsa and id_rsa.pub in ~/.ssh.  Note
 that para_server won't accept keys shorter than 2048 bits. Moreover,
@@ -353,7 +465,7 @@ Finally, tell para_client to connect to server_host:
        echo 'hostname server_host' > $conf
 
 
-### Start para_server ###
+<h3> Start para_server </h3>
 
 For this first try, we'll use the info loglevel to make the output
 of para_server more verbose.
@@ -369,7 +481,7 @@ commands. Open a new shell as bar@client_host and try
 to retrieve the list of available commands and some server info.
 Don't proceed if this doesn't work.
 
-### Create and populate the database ###
+<h3> Create and populate the database </h3>
 
 An empty database is created with
 
@@ -397,7 +509,7 @@ You may print the list of all known audio files with
 
        para_client ls
 
-### Configure para_audiod ###
+<h3> Configure para_audiod </h3>
 
 We will have to tell para_audiod that it should receive the audio
 stream from server_host via http:
@@ -418,14 +530,6 @@ in which order.
 Troubleshooting
 ---------------
 
-If you receive a socket related error on server or audiod startup,
-make sure you have write permissions to the /var/paraslash directory:
-
-       sudo chown $LOGNAME /var/paraslash
-
-Alternatively, use the --afs-socket (para_server) or --socket
-(para_audiod) option to specify a different socket pathname.
-
 To identify streaming problems try to receive, decode and play the
 stream manually using para_recv, para_filter and para_write as follows.
 For simplicity we assume that you're running Linux/ALSA and that only
@@ -448,9 +552,9 @@ User management
 para_server uses a challenge-response mechanism to authenticate
 requests from incoming connections, similar to ssh's public key
 authentication method. Authenticated connections are encrypted using
-a stream cipher, either RC4 or AES in integer counter mode.
+the AES stream cipher in integer counter mode.
 
-In this chapter we briefly describe RSA, RC4 and AES, and sketch the
+In this chapter we briefly describe RSA and AES, and sketch the
 [authentication handshake](#Client-server.authentication)
 between para_client and para_server. User management is discussed
 in the section on [the user_list file](#The.user_list.file).
@@ -458,33 +562,33 @@ These sections are all about communication between the client and the
 server. Connecting para_audiod is a different matter and is described
 in a [separate section](#Connecting.para_audiod).
 
-RSA, RC4, AES
--------------
+RSA and AES
+-----------
 
-RSA is an asymmetric block cipher which is used in many applications,
-including ssh and gpg. An RSA key consists in fact of two keys,
+A block cipher is a transformation which operates on fixed-length
+blocks. For symmetric block ciphers the transformation is determined
+by a single key for both encryption and decryption. For asymmetric
+block ciphers, on the other hand, the key consists of two parts,
 called the public key and the private key. A message can be encrypted
-with either key and only the counterpart of that key can decrypt
-the message. While RSA can be used for both signing and encrypting
-a message, paraslash uses RSA only for the latter purpose. The
-RSA public key encryption and signatures algorithms are defined in
-detail in RFC 2437.
-
-RC4 is a stream cipher, i.e. the input is XORed with a pseudo-random
-key stream to produce the output. Decryption uses the same function
-calls as encryption. While RC4 supports variable key lengths,
-paraslash uses a fixed length of 256 bits, which is considered a
-strong encryption by today's standards. Since the same key must never
-be used twice, a different, randomly-generated key is used for every
-new connection.
+with either key and only the counterpart of that key can decrypt the
+message. Asymmetric block ciphers can be used for both signing and
+encrypting a message.
+
+RSA is an asymmetric block cipher which is used in many applications,
+including ssh and gpg. The RSA public key encryption and signatures
+algorithms are defined in detail in RFC 2437. Paraslash relies on
+RSA for authentication.
+
+Stream ciphers XOR the input with a pseudo-random key stream to produce
+the output. Decryption uses the same function calls as encryption.
+Any block cipher can be turned into a stream cipher by generating the
+pseudo-random key stream by encrypting successive values of a counter
+(counter mode).
 
 AES, the advanced encryption standard, is a well-known symmetric block
-cipher, i.e. a transformation operating on fixed-length blocks which
-is determined by a single key for both encryption and decryption. Any
-block cipher can be turned into a stream cipher by generating
-a pseudo-random key stream by encrypting successive values of a
-counter. The AES_CTR128 stream cipher used in paraslash is obtained
-in this way from the AES block cipher with a 128 bit block size.
+cipher. Paraslash employs AES in counter mode as described above to
+encrypt communications. Since a stream cipher key must not be used
+twice, a random key is generated for every new connection.
 
 Client-server authentication
 ----------------------------
@@ -524,8 +628,8 @@ point on the communication is encrypted using the stream cipher with
 the session key known to both peers.
 
 paraslash relies on the quality of the pseudo-random bytes provided
-by the crypto library (openssl or libgcrypt), on the security of the
-implementation of the RSA, RC4 and AES crypto routines and on the
+by the crypto library (openssl or libgcrypt), on the security of
+the implementation of the RSA and AES crypto routines and on the
 infeasibility to invert the SHA1 function.
 
 Neither para_server or para_client create RSA keys on their
@@ -567,8 +671,7 @@ execute. The output of
 
        para_client help
 
-contains in the third column the permissions needed to execute the
-command.
+contains the permissions needed to execute the command.
 
 It is possible to make para_server reread the user_list file by
 executing the paraslash "hup" command or by sending SIGHUP to the
@@ -594,27 +697,21 @@ The audio file selector
 paraslash comes with a sophisticated audio file selector (AFS),
 whose main task is to determine which file to stream next, based on
 information on the audio files stored in a database. It communicates
-also with para_client whenever an AFS command is executed, for example
-to answer a database query.
+also with para_client via the command handler whenever an AFS command
+is executed, for example to answer a database query.
 
-Besides the traditional playlists, AFS supports audio file selection
+Besides the simple playlists, AFS supports audio file selection
 based on _moods_ which act as a filter that limits the set of all
-known audio files to those which satisfy certain criteria.  It also
+known audio files to those which satisfy certain criteria. It also
 maintains tables containing images (e.g. album cover art) and lyrics
 that can be associated with one or more audio files.
 
-AFS employs [libosl](http://people.tuebingen.mpg.de/maan/osl/), the
-object storage layer library, as the backend library for storing
-information on audio files, playlists, etc. This library offers
-functionality similar to a relational database, but is much more
-lightweight than a full database backend.
-
 In this chapter we sketch the setup of the [AFS
 process](#The.AFS.process) during server startup and proceed with the
 description of the [layout](#Database.layout) of the various database
 tables. The section on [playlists and moods](#Playlists.and.moods)
 explains these two audio file selection mechanisms in detail
-and contains pratical examples. The way [file renames and content
+and contains practical examples. The way [file renames and content
 changes](#File.renames.and.content.changes) are detected is discussed
 briefly before the [Troubleshooting](#Troubleshooting) section
 concludes the chapter.
@@ -623,25 +720,85 @@ The AFS process
 ---------------
 
 On startup, para_server forks to create the AFS process which opens
-the OSL database tables. The server process communicates with the
-AFS process via pipes and shared memory. Usually, the AFS process
-awakes only briefly whenever the current audio file changes. The AFS
-process determines the next audio file, opens it, verifies it has
-not been changed since it was added to the database and passes the
-open file descriptor to the server process, along with audio file
-meta-data such as file name, duration, audio format and so on. The
-server process then starts to stream the audio file.
-
-The AFS process also accepts connections from local clients via
-a well-known socket. However, only child processes of para_server
-may connect through this socket. All server commands that have the
-AFS_READ or AFS_WRITE permission bits use this mechanism to query or
-change the database.
+the database tables. The AFS process accepts incoming connections
+which arrive either on a pipe which is shared with para_server,
+or on the local socket it is listening on. The setup is as follows.
+
+                 .___________________.  .______________.
+                 |                   |  |              |
+                 | virtual streaming |  | audio format |
+                 |      system       |  |   handler    |
+                 |_________   _______|  |_____   ______|
+                           \ /                \ /
+                            |                  |
+         .-'""""`-.         |                  |          .-'""""`-.
+        (          )        |                  |         (          )
+        |`-.____.-'|    .__/ \________________/ \___.    |`-.____.-'|
+        |          |    |                           |    |          |
+        |   file   |----| AFS (audio file selector) |----|    OSL   |
+        |  system  |    |         process           |    | database |
+        |          |    |___________________________|    |          |
+        |. ' "" ` .|                 |                   |. ' "" ` .|
+        |          |                 |                   |          |
+         `-.____.-'                  |                    `-.____.-'
+                            ._______/ \_______.
+                            |                 |
+                            | command handler |
+                            |_______   _______|
+                                    \ /
+                                     |
+                                     |
+                                     |
+                              ._____/ \_____.
+                              |             |
+                              | para_client |
+                              |_____________|
+
+The virtual streaming system, which is part of the server process,
+communicates with the AFS process via pipes and shared memory. When
+the current audio file changes, it sends a notification through the
+shared pipe. The AFS process queries the database to determine the
+next audio file, opens it, verifies that it has not been changed since
+it was added to the database and passes the open file descriptor back
+to the virtual streaming system, along with audio file meta-data such
+as file name, duration, audio format and so on. The virtual streaming
+system then starts to stream the file.
+
+The command handlers of all AFS server commands use the local socket
+to query or update the database. For example, the command handler of
+the add command sends the path of an audio file to the local socket.
+The AFS process opens the file and tries to find an audio format
+handler which recognizes the file. If all goes well, a new database
+entry with metadata obtained from the audio format handler is added
+to the database.
+
+Note that AFS employs
+[libosl](http://people.tuebingen.mpg.de/maan/osl/), the object
+storage layer library, as the database backend. This library offers
+functionality similar to a relational database, but is much more
+lightweight than a full featured database management system.
 
 Database layout
 ---------------
 
-### The audio file table ###
+Metadata about the known audio files is stored in an OSL database. This
+database consists of the following tables:
+
+- The audio file table contains path, hash and metadata of each
+known file.
+
+- The "attributes" table maps each of the 64 possible attributes to a
+string.
+
+- The "blob" tables store images, lyrics, moods, playlists. All of
+these are optional.
+
+- The "score" table describes the subset of admissible files for the
+current playlist or mood.
+
+All tables are described in more detail below.
+
+<h3> The audio file table </h3>
 
 This is the most important and usually also the largest table of the
 AFS database. It contains the information needed to stream each audio
@@ -690,7 +847,7 @@ directory. In the latter case, the directory is traversed recursively
 and all files which are recognized as valid audio files are added to
 the database.
 
-### The attribute table ###
+<h3> The attribute table </h3>
 
 The attribute table contains two columns, _name_ and _bitnum_. An
 attribute is simply a name for a certain bit number in the attribute
@@ -750,7 +907,7 @@ Read the output of
 for more information and a complete list of command line options to
 these commands.
 
-### Blob tables ###
+<h3> Blob tables </h3>
 
 The image, lyrics, moods and playlists tables are all blob tables.
 Blob tables consist of three columns each: The identifier which is
@@ -777,7 +934,7 @@ Note that the images and lyrics are not interpreted at all, and also
 the playlist and the mood blobs are only investigated when the mood
 or playlist is activated with the select command.
 
-### The score table ###
+<h3> The score table </h3>
 
 The score table describes those audio files which are admissible for
 the current mood or playlist (see below). The table has two columns:
@@ -803,7 +960,7 @@ terms of attributes and other type of information available in the
 audio file table. As an example, a mood can define a filename pattern,
 which is then matched against the names of audio files in the table.
 
-### Playlists ###
+<h3> Playlists </h3>
 
 Playlists are accommodated in the playlist table of the afs database,
 using the aforementioned blob format for tables. A new playlist is
@@ -822,128 +979,144 @@ in descending order so that files will be selected in order. If a
 file could not be opened for streaming, its entry is removed from
 the score table (but not from the playlist).
 
-### Moods ###
-
-A mood consists of a unique name and its *mood definition*, which is
-a set of *mood lines* containing expressions in terms of attributes
-and other data contained in the database.
-
-At any time at most one mood can be *active* which means that
-para_server is going to select only files from that subset of
-admissible files.
-
-So in order to create a mood definition one has to write a set of
-mood lines. Mood lines come in three flavours: Accept lines, deny
-lines and score lines.
-
-The general syntax of the three types of mood lines is
-
-
-       accept [with score <score>] [if] [not] <mood_method> [options]
-       deny [with score <score>] [if] [not] <mood_method> [options]
-       score <score>  [if] [not] <mood_method> [options]
-
-
-Here <score> is either an integer or the string "random" which assigns
-a random score to all matching files. The score value changes the
-order in which admissible files are going to be selected, but is of
-minor importance for this introduction.
-
-So we concentrate on the first two forms, i.e. accept and deny
-lines. As usual, everything in square brackets is optional, i.e.
-accept/deny lines take the following form when ignoring scores:
-
-       accept [if] [not] <mood_method> [options]
-
-and analogously for the deny case. The "if" keyword is only syntactic
-sugar and has no function. The "not" keyword just inverts the result,
-so the essence of a mood line is the mood method part and the options
-following thereafter.
-
-A *mood method* is realized as a function which takes an audio file
-and computes a number from the data contained in the database.
-If this number is non-negative, we say the file *matches* the mood
-method. The file matches the full mood line if it either
-
-       - matches the mood method and the "not" keyword is not given,
-or
-       - does not match the mood method, but the "not" keyword is given.
-
-The set of admissible files for the whole mood is now defined as those
-files which match at least one accept mood line, but no deny mood line.
-More formally, an audio file F is admissible if and only if
+<h3> Moods </h3>
+
+A mood consists of a unique name and a definition. The definition
+is an expression which describes which audio files are considered
+admissible. At any time at most one mood can be active, meaning
+that para_server will only stream files which are admissible for the
+active mood.
+
+The expression may refer to attributes and other metadata stored in
+the database. Expressions may be combined by means of logical and
+arithmetical operators in a natural way. Moreover, string matching
+based on regular expression or wildcard patterns is supported.
+
+The set of admissible files is determined by applying the expression
+to each audio file in turn. For a mood definition to be valid, its
+expression must evaluate to a number, a string or a boolean value
+("true" or "false"). For numbers, any value other than zero means the
+file is admissible. For strings, any non-empty string indicates an
+admissible file. For boolean values, true means admissible and false
+means not admissible.  As a special case, the empty expression treats
+all files as admissible.
+
+<h3> Mood grammar </h3>
+
+Expressions are based on a context-free grammar which distinguishes
+between several types for syntactic units or groupings. The grammar
+defines a set of keywords which have a type and a corresponding
+semantic value, as shown in the following table.
+
+Keyword              |    Type | Semantic value
+:--------------------|--------:|:----------------------------------
+`path`               |  string | Full path of the current audio file
+`artist`             |  string | Content of the artist meta tag
+`title`              |  string | Content of the title meta tag
+`album`              |  string | Content of the album meta tag
+`comment`            |  string | Content of the somment meta tag
+`num_attributes_set` | integer | Number of attributes which are set
+`year`               | integer | Content of the year meta tag [\*]
+`num_played`         | integer | How many times the file has been streamed
+`image_id`           | integer | The identifier of the (cover art) image
+`lyrics_id`          | integer | The identifier of the lyrics blob
+`bitrate`            | integer | The average bitrate
+`frequency`          | integer | The output sample rate
+`channels`           | integer | The number of channels
+`is_set("foo")`      | boolean | True if attribute "foo" is set.
+
+[\*] For most audio formats, the year tag is stored as a string. It
+is converted to an integer by the mood parser. If the audio file
+has no year tag or the content of the year tag is not a number, the
+semantic value is zero. A special convention applies if the year tag
+is a one-digit or a two-digit number. In this case 1900 is added to
+the tag value.
+
+Expressions may be grouped using parentheses, logical and
+arithmetical operators or string matching operators. The following
+table lists the available operators.
+
+Token  | Meaning
+:------|:-------
+`\|\|` | Logical Or
+`&&`   | Logical And
+`!`    | Logical Not
+`==`   | Equal (can be applied to all types)
+`!=`   | Not equal. Likewise
+`<`    | Less than
+`<=`   | Less or equal
+`>=`   | Greater or equal
+`+`    | Arithmetical minus
+`-`    | Binary/unary minus
+`*`    | Multiplication
+`/`    | Division
+`=~`   | Regular expression match
+`=\|`  | Filename match
+
+Besides integers, strings and booleans there is an additional type
+which describes regular expression or wildcard patterns. Patterns
+are not just strings because they also include a list of flags which
+modify matching behaviour.
+
+Regular expression patterns are of the form `/pattern/[flags]`. That
+is, the pattern is delimited by slashes, and is followed by zero or
+more characters, each specifying a flag according to the following
+table
+
+Flag |    POSIX name | Meaning
+:----|--------------:|--------
+`i`  |   `REG_ICASE` | Ignore case in match
+`n`  | `REG_NEWLINE` | Treat newline as an ordinary character
+
+Note that only extended regular expression patterns are supported. See
+regex(3) for details.
+
+Wildcard patterns are similar, but the pattern must be delimited by
+`'|'` characters rather than slashes. For wildcard patterns different
+flags exist, as shown below.
+
+Flag |             POSIX name | Meaning
+:----|-----------------------:|--------
+`n`  | `FNM_NOESCAPE`         | Treat backslash as an ordinary character
+`p`  | `FNM_PATHNAME`         | Match a slash only with a slash in pattern
+`P`  | `FNM_PERIOD`           | Leading period has to be matched exactly
+`l`  | `FNM_LEADING_DIR` [\*] | Ignore "/\*" rest after successful matching
+`i`  | `FNM_CASEFOLD` [\*]    | Ignore case in match
+`e`  | `FNM_EXTMATCH` [\*\*]  | Enable extended pattern matching
+
+[\*] Not in POSIX, but both FreeBSD and NetBSD have it.
+
+[\*\*] GNU extension, silently ignored on non GNU systems.
+
+See fnmatch(3) for details.
+
+Mood definitions may contain arbitrary whitespace and comments.
+A comment is a word beginning with #. This word and all remaining
+characters of the line are ignored.
+
+<h3> Example moods </h3>
+
+* Files with no/invalid year tag: `year == 0`
+
+* Only oldies: `year != 0 && year < 1980`
+
+* Only 80's Rock or Metal: `(year >= 1980 && year < 1990) &&
+  (is_set("rock") || is_set("metal"))`
+
+* Files with incomplete tags: `artist == "" || title == "" || album =
+"" || comment == "" || year == 0`
+
+* Files with no attributes defined so far: `num_attributes_set == 0`
+
+* Only newly added files: `num_played == 0`
+
+* Only poor quality files: `bitrate < 96`
+
+* Cope with different spellings of Motörhead: `artist =~ /mot(ö|oe{0,1})rhead/i`
+
+* The same with extended wildcard patterns: `artist =| |mot+(o\|oe\|ö)rhead|ie`
 
-       (F ~ AL1 or F ~ AL2...) and not (F ~ DL1 or F ~ DN2 ...)
-
-where AL1, AL2... are the accept lines, DL1, DL2... are the deny
-lines and "~" means "matches".
-
-The cases where no mood lines of accept/deny type are defined need
-special treatment:
-
-       - Neither accept nor deny lines: This treats all files as
-       admissible (in fact, that is the definition of the dummy mood
-       which is activated automatically if no moods are available).
-
-       - Only accept lines: A file is admissible iff it matches at
-       least one accept line:
-
-               F ~ AL1 or F ~ AL2 or ...
-
-       - Only deny lines: A file is admissible iff it matches no
-       deny line:
-
-               not (F ~ DL1 or F ~ DN2 ...)
-
-
-
-### List of mood_methods ###
-
-       no_attributes_set
-
-Takes no arguments and matches an audio file if and only if no
-attributes are set.
-
-       is_set <attribute_name>
-
-Takes the name of an attribute and matches iff that attribute is set.
-
-       path_matches <pattern>
-
-Takes a filename pattern and matches iff the path of the audio file
-matches the pattern.
-
-       artist_matches <pattern>
-       album_matches <pattern>
-       title_matches <pattern>
-       comment_matches <pattern>
-
-Takes an extended regular expression and matches iff the text of the
-corresponding tag of the audio file matches the pattern. If the tag
-is not set, the empty string is matched against the pattern.
-
-       year ~ <num>
-       bitrate ~ <num>
-       frequency ~ <num>
-       channels ~ <num>
-       num_played ~ <num>
-       image_id ~ <num>
-       lyrics_id ~ <num>
-
-Takes a comparator ~ of the set {<, =, <=, >, >=, !=} and a number
-<num>. Matches an audio file iff the condition <val> ~ <num> is
-satisfied where val is the corresponding value of the audio file
-(value of the year tag, bitrate in kbit/s, etc.).
-
-The year tag is special as its value is undefined if the audio file
-has no year tag or the content of the year tag is not a number. Such
-audio files never match. Another difference is the special treatment
-if the year tag is a two-digit number. In this case either 1900 or
-2000 is added to the tag value, depending on whether the number is
-greater than 2000 plus the current year.
-
-
-### Mood usage ###
+<h3> Mood usage </h3>
 
 To create a new mood called "my_mood", write its definition into
 some temporary file, say "tmpfile", and add it to the mood table
@@ -970,27 +1143,6 @@ if the "-a" switch is given:
 
        para ls -a
 
-
-### Example mood definition ###
-
-Suppose you have defined attributes "punk" and "rock" and want to define
-a mood containing only Punk-Rock songs. That is, an audio file should be
-admissible if and only if both attributes are set. Since
-
-       punk and rock
-
-is obviously the same as
-
-       not (not punk or not rock)
-
-(de Morgan's rule), a mood definition that selects only Punk-Rock
-songs is
-
-       deny if not is_set punk
-       deny if not is_set rock
-
-
-
 File renames and content changes
 --------------------------------
 
@@ -1060,7 +1212,7 @@ Audio formats
 
 The following audio formats are supported by paraslash:
 
-### MP3 ###
+<h3> MP3 </h3>
 
 Mp3, MPEG-1 Audio Layer 3, is a common audio format for audio storage,
 designed as part of its MPEG-1 standard.  An MP3 file is made up of
@@ -1070,7 +1222,7 @@ of channels. For a typical CD-audio file (sample rate of 44.1 kHz
 stereo), encoded with a bit rate of 128 kbit, an MP3 frame is about
 400 bytes large.
 
-### OGG/Vorbis ###
+<h3> OGG/Vorbis </h3>
 
 OGG is a standardized audio container format, while Vorbis is an
 open source codec for lossy audio compression. Since Vorbis is most
@@ -1080,7 +1232,7 @@ chunks called OGG pages. A typical OGG page is about 4KB large. The
 Vorbis codec creates variable-bitrate (VBR) data, where the bitrate
 may vary considerably.
 
-### OGG/Speex ###
+<h3> OGG/Speex </h3>
 
 Speex is an open-source speech codec that is based on CELP (Code
 Excited Linear Prediction) coding. It is designed for voice
@@ -1090,7 +1242,7 @@ supported. As for Vorbis audio, Speex bit-streams are often stored
 in OGG files. As of 2012 this codec is considered obsolete since the
 Oppus codec, described below, surpasses its performance in all areas.
 
-### OGG/Opus ###
+<h3> OGG/Opus </h3>
 
 Opus is a lossy audio compression format standardized through RFC
 6716 in 2012. It combines the speech-oriented SILK codec and the
@@ -1099,7 +1251,7 @@ OGG/Vorbis and OGG/Speex, Opus data is usually encapsulated in OGG
 containers. All known software patents which cover Opus are licensed
 under royalty-free terms.
 
-### AAC ###
+<h3> AAC </h3>
 
 Advanced Audio Coding (AAC) is a standardized, lossy compression
 and encoding scheme for digital audio which is the default audio
@@ -1107,7 +1259,7 @@ format for Apple's iPhone, iPod, iTunes. Usually MPEG-4 is used as
 the container format and audio files encoded with AAC have the .m4a
 extension. A typical AAC frame is about 700 bytes large.
 
-### WMA ###
+<h3> WMA </h3>
 
 Windows Media Audio (WMA) is an audio data compression technology
 developed by Microsoft. A WMA file is usually encapsulated in the
@@ -1116,7 +1268,7 @@ how meta data about the file is to be encoded. The bit stream of WMA
 is composed of superframes, each containing one or more frames of
 2048 samples. For 16 bit stereo a WMA superframe is about 8K large.
 
-### FLAC ###
+<h3> FLAC </h3>
 
 The Free Lossless Audio Codec (FLAC) compresses audio without quality
 loss. It gives better compression ratios than a general purpose
@@ -1287,7 +1439,7 @@ only for Linux.
 
 - UDP. Recommended for multicast LAN streaming.
 
-See the Appendix on [network protocols](/#Network.protocols)
+See the Appendix on [network protocols](#Network.protocols)
 for brief descriptions of the various protocols relevant for network
 audio streaming with paraslash.
 
@@ -1389,27 +1541,6 @@ currently running server process.
 
        para_client si
 
-The sender command of para_server prints information about senders,
-like the various access control lists, and it allows to (de-)activate
-senders and to change the access permissions at runtime.
-
--> List all senders
-
-       para_client sender
-
--> Obtain general help for the sender command:
-
-       para_client help sender
-
--> Get help for a specific sender (contains further examples):
-
-       s=http # or dccp or udp
-       para_client sender $s help
-
--> Show status of the http sender
-
-       para_client sender http status
-
 By default para_server activates both the HTTP and th DCCP sender on
 startup. This can be changed via command line options or para_server's
 config file.
@@ -1418,13 +1549,6 @@ config file.
 
        para_server -h
 
-All senders share the "on" and "off" commands, so senders may be
-activated and deactivated independently of each other.
-
--> Switch off the http sender:
-
-       para_client sender http off
-
 -> Receive a DCCP stream using CCID2 and write the output into a file:
 
        host=foo.org; ccid=2; filename=bar
@@ -1435,20 +1559,11 @@ receiver has its own set of command line options and its own command
 line parser, so arguments for the dccp receiver must be protected
 from being interpreted by para_recv.
 
--> Start UDP multicast, using the default multicast address:
-
-       para_client sender udp add 224.0.1.38
-
 -> Receive FEC-encoded multicast stream and write the output into a file:
 
        filename=foo
        para_recv -r udp > $filename
 
--> Add an UDP unicast for a client to the target list of the UDP sender:
-
-       t=client.foo.org
-       para_client sender udp add $t
-
 -> Receive this (FEC-encoded) unicast stream:
 
        filename=foo
@@ -1581,7 +1696,7 @@ they are usually placed directly after the decoding filter. Each
 sample is multiplied with a scaling factor (>= 1) which makes amp
 and compress quite expensive in terms of computing power.
 
-### amp ###
+<h3> amp </h3>
 
 The amp filter amplifies the audio stream by a fixed scaling factor
 that must be known in advance. For para_audiod this factor is derived
@@ -1608,7 +1723,7 @@ To store V in the audio file table, the command
 is used. The reader is encouraged to write a script that performs
 these computations :)
 
-### compress ###
+<h3> compress </h3>
 
 Unlike the amplification filter, the compress filter adjusts the volume
 of the audio stream dynamically without prior knowledge about the peak
@@ -1626,7 +1741,7 @@ These filters are rather simple and do not modify the audio stream at
 all. The wav filter is only useful with para_filter and in connection
 with a decoder. It asks the decoder for the number of channels and the
 sample rate of the stream and adds a Microsoft wave header containing
-this information at the beginning. This allows to write wav files
+this information at the beginning. This allows writing wav files
 rather than raw PCM files (which do not contain any information about
 the number of channels and the sample rate).
 
@@ -1640,17 +1755,6 @@ Both filters require almost no additional computing time, even when
 operating on uncompressed audio streams, since data buffers are simply
 "pushed down" rather than copied.
 
-Examples
---------
-
--> Decode an mp3 file to wav format:
-
-       para_filter -f mp3dec -f wav < file.mp3 > file.wav
-
--> Amplify a raw audio file by a factor of 1.5:
-
-       para_filter -f amp --amp 32 < foo.raw > bar.raw
-
 ======
 Output
 ======
@@ -1700,12 +1804,8 @@ emulation for backwards compatibility. This API is rather simple but
 also limited. For example only one application can open the device
 at any time. The OSS writer is activated by default on BSD Systems.
 
-- *OSX*. Mac OS X has yet another API called CoreAudio. The OSX writer
-for this API is only compiled in on such systems and is of course
-the default there.
-
-- *FILE*. The file writer allows to capture the audio stream and
-write the PCM data to a file on the file system rather than playing
+- *FILE*. The file writer allows capturing the audio stream and
+writing the PCM data to a file on the file system rather than playing
 it through a sound device. It is supported on all platforms and is
 always compiled in.
 
@@ -1833,12 +1933,14 @@ welcome. Here's a list of things you can do to help the project:
 - Compile and test on your favorite architecture or operating
   system. The code is tested only on a limited set of systems, so you
   will probably encounter problems when building on different systems.
-- Post about about paraslash on your blog or on social networks.
+- Post about paraslash on your blog or on social networks.
 - Build and maintain Debian/RPM packages for your favorite distribution.
 
 Note that there is no mailing list, no bug tracker and no discussion
 forum for paraslash. If you'd like to contribute, or have questions
 about contributing, send email to Andre Noll <maan@tuebingen.mpg.de>.
+New releases are announced by email. If you would like to receive
+these announcements, contact the author through the above address.
 
 Tools
 -----
@@ -1857,7 +1959,7 @@ and for getting updates.
 the configure file which is shipped in the tarballs but has to be
 generated when compiling from git.
 
-- [discount](http://www.pell.portland.or.us/~orc/Code/discount). The
+- [discount](http://www.pell.portland.or.us/~orc/Code/discount/). The
 HTML version of this manual and some of the paraslash web pages are
 written in the Markdown markup language and are translated into html
 with the converter of the *Discount* package.
@@ -1961,7 +2063,7 @@ Coding Style
 
 The preferred coding style for paraslash coincides more or less
 with the style of the Linux kernel. So rather than repeating what is
-written [there](http://www.kernel.org/doc/Documentation/process/coding-style.rst),
+written [there](https://www.kernel.org/doc/Documentation/process/coding-style.rst),
 here are the most important points.
 
 - Burn the GNU coding standards.
@@ -2027,14 +2129,14 @@ Appendix
 Network protocols
 -----------------
 
-### IP ###
+<h3> IP </h3>
 
 The _Internet Protocol_ is the primary networking protocol used for
 the Internet. All protocols described below use IP as the underlying
 layer. Both the prevalent IPv4 and the next-generation IPv6 variant
 are being deployed actively worldwide.
 
-### Connection-oriented and connectionless protocols ###
+<h3> Connection-oriented and connectionless protocols </h3>
 
 Connectionless protocols differ from connection-oriented ones in
 that state associated with the sending/receiving endpoints is treated
@@ -2050,7 +2152,7 @@ up-to-date internal state of the connection also in general means
 that the sending endpoints perform congestion control, adapting to
 qualitative changes of the connection medium.
 
-### Reliability ###
+<h3> Reliability </h3>
 
 In IP networking, packets can be lost, duplicated, or delivered
 out of order, and different network protocols handle these
@@ -2062,7 +2164,7 @@ out-of-order. Retransmission is used to guarantee loss-free
 delivery. Unreliable protocols, in contrast, do not guarantee ordering
 or data integrity.
 
-### Classification ###
+<h3> Classification </h3>
 
 With these definitions the protocols which are used by paraslash for
 steaming audio data may be classified as follows.
@@ -2073,7 +2175,7 @@ steaming audio data may be classified as follows.
 
 Below we give a short descriptions of these protocols.
 
-### TCP ###
+<h3> TCP </h3>
 
 The _Transmission Control Protocol_ provides reliable, ordered delivery
 of a stream and a classic window-based congestion control. In contrast
@@ -2084,7 +2186,7 @@ extensively by many application layers. Besides HTTP (the Hypertext
 Transfer Protocol), also FTP (the File Transfer protocol), SMTP (Simple
 Mail Transfer Protocol), SSH (Secure Shell) all sit on top of TCP.
 
-### UDP ###
+<h3> UDP </h3>
 
 The _User Datagram Protocol_ is the simplest transport-layer protocol,
 built as a thin layer directly on top of IP. For this reason, it offers
@@ -2095,7 +2197,7 @@ means that there is no protection against packet loss or network
 congestion. Error checking and correction (if at all) are performed
 in the application.
 
-### DCCP ###
+<h3> DCCP </h3>
 
 The _Datagram Congestion Control Protocol_ combines the
 connection-oriented state maintenance known from TCP with the
@@ -2112,7 +2214,7 @@ the choice of congestion control: classic, window-based congestion
 control known from TCP is available as CCID-2, rate-based, "smooth"
 congestion control is offered as CCID-3.
 
-### HTTP ###
+<h3> HTTP </h3>
 
 The _Hypertext Transfer Protocol_ is an application layer protocol
 on top of TCP. It is spoken by web servers and is most often used
@@ -2122,7 +2224,7 @@ delivery of web pages only. Being a simple request/response based
 protocol, the semantics of the protocol also allow the delivery of
 multimedia content, such as audio over http.
 
-### Multicast ###
+<h3> Multicast </h3>
 
 IP multicast is not really a protocol but a technique for one-to-many
 communication over an IP network. The challenge is to deliver
@@ -2224,17 +2326,17 @@ Application web pages
 ---------------------
 
 - [paraslash](http://people.tuebingen.mpg.de/maan/paraslash/)
-- [xmms](http://xmms2.org/wiki/Main_Page)
+- [xmms](https://xmms2.org/wiki/Main_Page)
 - [mpg123](http://www.mpg123.de/)
-- [gstreamer](http://gstreamer.freedesktop.org/)
+- [gstreamer](https://gstreamer.freedesktop.org/)
 - [icecast](http://www.icecast.org/)
-- [Audio Compress](http://beesbuzz.biz/code/audiocompress.php)
+- [Audio Compress](https://beesbuzz.biz/code/audiocompress.php)
 
 External documentation
 ----------------------
 
 - [The mathematics of
-Raid6](http://kernel.org/pub/linux/kernel/people/hpa/raid6.pdf)
+Raid6](https://www.kernel.org/pub/linux/kernel/people/hpa/raid6.pdf)
 by H. Peter Anvin
 
 - [Effective Erasure Codes for reliable Computer Communication
index b4935f0d44f30fc6091f8431b99a63f23674aed6..63e49677b0f1b85c13c17160a7b249531c1fbcd4 100644 (file)
--- a/wma_afh.c
+++ b/wma_afh.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file wma_afh.c The audio format handler for WMA files. */
 
@@ -38,8 +34,7 @@ static int count_frames(const char *buf, int buf_size, uint32_t packet_size,
                sfc++;
        }
        PARA_INFO_LOG("%d frames, %d superframes\n", fc, sfc);
-       if (num_superframes)
-               *num_superframes = sfc;
+       *num_superframes = sfc;
        return fc;
 }
 
@@ -68,7 +63,7 @@ static int put_utf8(uint32_t val, char *result)
                *out++ = in;
                return 1;
        }
-       bytes = (wma_log2(in) + 4) / 5;
+       bytes = DIV_ROUND_UP(wma_log2(in), 5);
        shift = (bytes - 1) * 6;
        *out++ = (256 - (256 >> bytes)) | (in >> shift);
        while (shift >= 6) {
@@ -229,6 +224,7 @@ static int wma_make_chunk_table(char *buf, size_t buf_size, uint32_t packet_size
                }
        }
        afhi->chunks_total = j;
+       set_max_chunk_size(afhi);
        set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
        return 1;
 fail:
@@ -365,8 +361,7 @@ static int make_cdo(struct taginfo *tags, const struct asf_object *cdo,
                struct asf_object *result)
 {
        const char *cr, *rating; /* orig data */
-       uint16_t orig_title_bytes, orig_artist_bytes, orig_cr_bytes,
-               orig_comment_bytes, orig_rating_bytes;
+       uint16_t orig_cr_bytes, orig_rating_bytes;
        /* pointers to new UTF-16 tags */
        char *artist = NULL, *title = NULL, *comment = NULL;
        /* number of bytes in UTF-16 for the new tags */
@@ -378,17 +373,21 @@ static int make_cdo(struct taginfo *tags, const struct asf_object *cdo,
        ret = convert_utf8_to_utf16(tags->artist, &artist);
        if (ret < 0)
                return ret;
+       assert(artist);
        artist_bytes = ret;
        ret = convert_utf8_to_utf16(tags->title, &title);
        if (ret < 0)
                goto out;
+       assert(title);
        title_bytes = ret;
        ret = convert_utf8_to_utf16(tags->comment, &comment);
        if (ret < 0)
                goto out;
+       assert(comment);
        comment_bytes = ret;
 
        if (cdo) {
+               uint16_t orig_title_bytes, orig_artist_bytes, orig_comment_bytes;
                /*
                 * Sizes of the five fields (stored as 16-bit numbers) are
                 * located after the header (16 bytes) and the cdo size (8
@@ -402,10 +401,7 @@ static int make_cdo(struct taginfo *tags, const struct asf_object *cdo,
                cr = cdo->ptr + 34 + orig_title_bytes + orig_artist_bytes;
                rating = cr + orig_cr_bytes + orig_comment_bytes;
        } else {
-               orig_title_bytes = 2;
-               orig_artist_bytes = 2;
                orig_cr_bytes = 2;
-               orig_comment_bytes = 2;
                orig_rating_bytes = 2;
                cr = null;
                rating = null;
@@ -461,10 +457,12 @@ static int make_ecdo(struct taginfo *tags, struct asf_object *result)
        ret = convert_utf8_to_utf16(tags->album, &album);
        if (ret < 0)
                return ret;
+       assert(album);
        album_bytes = ret;
        ret = convert_utf8_to_utf16(tags->year, &year);
        if (ret < 0)
                goto out;
+       assert(year);
        year_bytes = ret;
        result->size = 16 + 8 + 2; /* GUID, size, count */
        /* name_length + name + null + data type + val length + val */
@@ -648,13 +646,13 @@ out:
 static const char * const wma_suffixes[] = {"wma", NULL};
 
 /**
- * The init function of the wma audio format handler.
+ * The audio format handler for Windows Media Audio.
  *
- * \param afh Pointer to the struct to initialize.
+ * Only WMA version 2 is supported. This audio format handler does not depend
+ * on any third party libraries and is therefore always compiled in.
  */
-void wma_afh_init(struct audio_format_handler *afh)
-{
-       afh->get_file_info = wma_get_file_info;
-       afh->suffixes = wma_suffixes;
-       afh->rewrite_tags = wma_rewrite_tags;
-}
+const struct audio_format_handler wma_afh = {
+       .get_file_info = wma_get_file_info,
+       .suffixes = wma_suffixes,
+       .rewrite_tags = wma_rewrite_tags,
+};
index 6d57c00be2d2627624f5642a74948723bc2726fb..b123b5d2104a24500b8123f94bc7e2c6b8622fbc 100644 (file)
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file wma_common.c Functions used by both the WMA afh and decoder. */
 
@@ -178,7 +174,5 @@ __a_const int wma_log2(unsigned int v)
                v >>= 8;
                n += 8;
        }
-       n += log2_tab[v];
-
-       return n;
+       return n + log2_tab[v];
 }
index 4c7c047a482e2b3f256cfb8c12dcd6b4de3a7500..edf50cb0b3834d8971fe9602f6528e2f2bd86e5d 100644 (file)
@@ -5,8 +5,7 @@
  *
  * Copyright (c) 2002 The FFmpeg Project
  *
- * Licensed under the GNU Lesser General Public License.
- * For licencing details see COPYING.LIB.
+ * Licensed under the GNU Lesser General Public License, see file COPYING.LIB.
  */
 
 /** \file wmadec_filter.c paraslash's WMA decoder. */
@@ -15,8 +14,6 @@
  * This decoder handles Microsoft Windows Media Audio data version 2.
  */
 
-#define _XOPEN_SOURCE 600
-
 #include <math.h>
 #include <regex.h>
 #include <sys/select.h>
 #include "para.h"
 #include "error.h"
 #include "list.h"
-#include "ggo.h"
 #include "string.h"
 #include "sched.h"
 #include "buffer_tree.h"
 #include "filter.h"
+#include "portable_io.h"
 #include "bitstream.h"
 #include "imdct.h"
 #include "wma.h"
@@ -81,7 +78,6 @@ struct private_wmadec_data {
        struct vlc coef_vlc[2];
        uint16_t *run_table[2];
        uint16_t *level_table[2];
-       const struct coef_vlc_table *coef_vlcs[2];
        /** Frame length in samples. */
        int frame_len;
        /** log2 of frame_len. */
@@ -100,8 +96,6 @@ struct private_wmadec_data {
        int block_len;
        /** Current position in frame. */
        int block_pos;
-       /** True if mid/side stereo mode. */
-       uint8_t ms_stereo;
        /** True if channel is coded. */
        uint8_t channel_coded[MAX_CHANNELS];
        /** log2 ratio frame/exp. length. */
@@ -159,51 +153,27 @@ static void sine_window_init(float *window, int n)
                window[i] = sinf((i + 0.5) * (M_PI / (2.0 * n)));
 }
 
-static void wmadec_cleanup(struct private_wmadec_data *pwd)
-{
-       int i;
-
-       for (i = 0; i < pwd->nb_block_sizes; i++)
-               imdct_end(pwd->mdct_ctx[i]);
-       if (pwd->ahi.use_exp_vlc)
-               free_vlc(&pwd->exp_vlc);
-       if (pwd->use_noise_coding)
-               free_vlc(&pwd->hgain_vlc);
-       for (i = 0; i < 2; i++) {
-               free_vlc(&pwd->coef_vlc[i]);
-               free(pwd->run_table[i]);
-               free(pwd->level_table[i]);
-       }
-}
-
-static void init_coef_vlc(struct vlc *vlc, uint16_t **prun_table,
-               uint16_t **plevel_table, const struct coef_vlc_table *vlc_table)
+static void init_coef_vlc(struct private_wmadec_data *pwd, int sidx, int didx)
 {
-       int n = vlc_table->n;
-       const uint8_t *table_bits = vlc_table->huffbits;
-       const uint32_t *table_codes = vlc_table->huffcodes;
-       const uint16_t *levels_table = vlc_table->levels;
-       uint16_t *run_table, *level_table;
-       int i, l, j, k, level;
-
-       init_vlc(vlc, VLCBITS, n, table_bits, table_codes, 4);
+       const struct coef_vlc_table *src = coef_vlcs + sidx;
+       struct vlc *dst = pwd->coef_vlc + didx;
+       int i, l, j, k, level, n = src->n;
 
-       run_table = para_malloc(n * sizeof(uint16_t));
-       level_table = para_malloc(n * sizeof(uint16_t));
+       init_vlc(dst, VLCBITS, n, src->huffbits, src->huffcodes, 4);
+       pwd->run_table[didx] = para_malloc(n * sizeof(uint16_t));
+       pwd->level_table[didx] = para_malloc(n * sizeof(uint16_t));
        i = 2;
        level = 1;
        k = 0;
        while (i < n) {
-               l = levels_table[k++];
+               l = src->levels[k++];
                for (j = 0; j < l; j++) {
-                       run_table[i] = j;
-                       level_table[i] = level;
+                       pwd->run_table[didx][i] = j;
+                       pwd->level_table[didx][i] = level;
                        i++;
                }
                level++;
        }
-       *prun_table = run_table;
-       *plevel_table = level_table;
 }
 
 /* compute the scale factor band sizes for each MDCT block size */
@@ -413,19 +383,15 @@ static int wma_init(struct private_wmadec_data *pwd)
        }
 
        /* choose the VLC tables for the coefficients */
-       coef_vlc_table = 2;
+       coef_vlc_table = 4;
        if (ahi->sample_rate >= 32000) {
                if (bps1 < 0.72)
                        coef_vlc_table = 0;
                else if (bps1 < 1.16)
-                       coef_vlc_table = 1;
+                       coef_vlc_table = 2;
        }
-       pwd->coef_vlcs[0] = &coef_vlcs[coef_vlc_table * 2];
-       pwd->coef_vlcs[1] = &coef_vlcs[coef_vlc_table * 2 + 1];
-       init_coef_vlc(&pwd->coef_vlc[0], &pwd->run_table[0], &pwd->level_table[0],
-               pwd->coef_vlcs[0]);
-       init_coef_vlc(&pwd->coef_vlc[1], &pwd->run_table[1], &pwd->level_table[1],
-               pwd->coef_vlcs[1]);
+       init_coef_vlc(pwd, coef_vlc_table, 0);
+       init_coef_vlc(pwd, coef_vlc_table + 1, 1);
        return 0;
 }
 
@@ -581,7 +547,7 @@ static int decode_exp_vlc(struct private_wmadec_data *pwd, int ch)
        last_exp = 36;
 
        while (q < q_end) {
-               code = get_vlc(&pwd->gb, pwd->exp_vlc.table, EXPVLCBITS);
+               code = get_vlc(&pwd->gb, &pwd->exp_vlc);
                if (code < 0)
                        return code;
                /* NOTE: this offset is the same as MPEG4 AAC ! */
@@ -708,8 +674,7 @@ static int compute_high_band_values(struct private_wmadec_data *pwd,
                        if (val == (int)0x80000000)
                                val = get_bits(&pwd->gb, 7) - 19;
                        else {
-                               int code = get_vlc(&pwd->gb,
-                                       pwd->hgain_vlc.table, HGAINVLCBITS);
+                               int code = get_vlc(&pwd->gb, &pwd->hgain_vlc);
                                if (code < 0)
                                        return code;
                                val += code - 18;
@@ -828,6 +793,7 @@ static int wma_decode_block(struct private_wmadec_data *pwd)
        int ret, n, v, ch, code, bsize;
        int coef_nb_bits, total_gain;
        int nb_coefs[MAX_CHANNELS];
+       bool ms_stereo = false; /* mid/side stereo mode */
 
        /* compute current block length */
        if (pwd->ahi.use_variable_block_len) {
@@ -865,7 +831,7 @@ static int wma_decode_block(struct private_wmadec_data *pwd)
                return -E_INCOHERENT_BLOCK_LEN;
 
        if (pwd->ahi.channels == 2)
-               pwd->ms_stereo = get_bit(&pwd->gb);
+               ms_stereo = get_bit(&pwd->gb);
        v = 0;
        for (ch = 0; ch < pwd->ahi.channels; ch++) {
                int a = get_bit(&pwd->gb);
@@ -931,7 +897,7 @@ static int wma_decode_block(struct private_wmadec_data *pwd)
                 * special VLC tables are used for ms stereo because there is
                 * potentially less energy there
                 */
-               tindex = (ch == 1 && pwd->ms_stereo);
+               tindex = ch == 1 && ms_stereo;
                coef_vlc = &pwd->coef_vlc[tindex];
                run_table = pwd->run_table[tindex];
                level_table = pwd->level_table[tindex];
@@ -940,7 +906,7 @@ static int wma_decode_block(struct private_wmadec_data *pwd)
                eptr = ptr + nb_coefs[ch];
                memset(ptr, 0, pwd->block_len * sizeof(int16_t));
                for (;;) {
-                       code = get_vlc(&pwd->gb, coef_vlc->table, VLCBITS);
+                       code = get_vlc(&pwd->gb, coef_vlc);
                        if (code < 0)
                                return code;
                        if (code == 1) /* EOB */
@@ -966,7 +932,7 @@ static int wma_decode_block(struct private_wmadec_data *pwd)
                }
        }
        compute_mdct_coefficients(pwd, bsize, total_gain, nb_coefs);
-       if (pwd->ms_stereo && pwd->channel_coded[1]) {
+       if (ms_stereo && pwd->channel_coded[1]) {
                float a, b;
                int i;
                /*
@@ -994,7 +960,7 @@ next:
                n4 = pwd->block_len / 2;
                if (pwd->channel_coded[ch])
                        imdct(pwd->mdct_ctx[bsize], pwd->output, pwd->coefs[ch]);
-               else if (!(pwd->ms_stereo && ch == 1))
+               else if (!(ms_stereo && ch == 1))
                        memset(pwd->output, 0, sizeof(pwd->output));
 
                /* multiply by the window and add in the frame */
@@ -1058,24 +1024,13 @@ static int wma_decode_frame(struct private_wmadec_data *pwd, int16_t *samples)
        return 0;
 }
 
-static int wma_decode_superframe(struct private_wmadec_data *pwd, void *data,
-               int *data_size, const uint8_t *buf, int buf_size)
+static int wma_decode_superframe(struct private_wmadec_data *pwd, void *out,
+               int *out_size, const uint8_t *in)
 {
-       int ret;
-       int16_t *samples;
+       int ret, in_size = pwd->ahi.packet_size - WMA_FRAME_SKIP;
+       int16_t *samples = out;
 
-       if (buf_size == 0) {
-               pwd->last_superframe_len = 0;
-               *data_size = 0;
-               return 0;
-       }
-       if (buf_size < pwd->ahi.block_align) {
-               *data_size = 0;
-               return 0;
-       }
-       buf_size = pwd->ahi.block_align;
-       samples = data;
-       init_get_bits(&pwd->gb, buf, buf_size);
+       init_get_bits(&pwd->gb, in, in_size);
        if (pwd->ahi.use_bit_reservoir) {
                int i, nb_frames, bit_offset, pos, len;
                uint8_t *q;
@@ -1086,7 +1041,7 @@ static int wma_decode_superframe(struct private_wmadec_data *pwd, void *data,
                // PARA_DEBUG_LOG("have %d frames\n", nb_frames);
                ret = -E_WMA_OUTPUT_SPACE;
                if ((nb_frames + 1) * pwd->ahi.channels * pwd->frame_len
-                               * sizeof(int16_t) > *data_size)
+                               * sizeof(int16_t) > *out_size)
                        goto fail;
 
                bit_offset = get_bits(&pwd->gb, pwd->byte_offset_bits + 3);
@@ -1124,7 +1079,7 @@ static int wma_decode_superframe(struct private_wmadec_data *pwd, void *data,
 
                /* read each frame starting from bit_offset */
                pos = bit_offset + 4 + 4 + pwd->byte_offset_bits + 3;
-               init_get_bits(&pwd->gb, buf + (pos >> 3),
+               init_get_bits(&pwd->gb, in + (pos >> 3),
                        (MAX_CODED_SUPERFRAME_SIZE - (pos >> 3)));
                len = pos & 7;
                if (len > 0)
@@ -1143,16 +1098,16 @@ static int wma_decode_superframe(struct private_wmadec_data *pwd, void *data,
                        ((bit_offset + 4 + 4 + pwd->byte_offset_bits + 3) & ~7);
                pwd->last_bitoffset = pos & 7;
                pos >>= 3;
-               len = buf_size - pos;
+               len = in_size - pos;
                ret = -E_WMA_BAD_SUPERFRAME;
                if (len > MAX_CODED_SUPERFRAME_SIZE || len < 0)
                        goto fail;
                pwd->last_superframe_len = len;
-               memcpy(pwd->last_superframe, buf + pos, len);
+               memcpy(pwd->last_superframe, in + pos, len);
        } else {
                PARA_DEBUG_LOG("not using bit reservoir\n");
                ret = -E_WMA_OUTPUT_SPACE;
-               if (pwd->ahi.channels * pwd->frame_len * sizeof(int16_t) > *data_size)
+               if (pwd->ahi.channels * pwd->frame_len * sizeof(int16_t) > *out_size)
                        goto fail;
                /* single frame decode */
                ret = wma_decode_frame(pwd, samples);
@@ -1162,8 +1117,8 @@ static int wma_decode_superframe(struct private_wmadec_data *pwd, void *data,
        }
        PARA_DEBUG_LOG("frame_len: %d, block_len: %d, outbytes: %d, eaten: %d\n",
                pwd->frame_len, pwd->block_len,
-               (int)((int8_t *)samples - (int8_t *)data), pwd->ahi.block_align);
-       *data_size = (int8_t *)samples - (int8_t *)data;
+               (int)((int8_t *)samples - (int8_t *)out), pwd->ahi.block_align);
+       *out_size = (int8_t *)samples - (int8_t *)out;
        return pwd->ahi.block_align;
 fail:
        /* reset the bit reservoir on errors */
@@ -1174,10 +1129,21 @@ fail:
 static void wmadec_close(struct filter_node *fn)
 {
        struct private_wmadec_data *pwd = fn->private_data;
+       int i;
 
        if (!pwd)
                return;
-       wmadec_cleanup(pwd);
+       for (i = 0; i < pwd->nb_block_sizes; i++)
+               imdct_end(pwd->mdct_ctx[i]);
+       if (pwd->ahi.use_exp_vlc)
+               free_vlc(&pwd->exp_vlc);
+       if (pwd->use_noise_coding)
+               free_vlc(&pwd->hgain_vlc);
+       for (i = 0; i < 2; i++) {
+               free_vlc(&pwd->coef_vlc[i]);
+               free(pwd->run_table[i]);
+               free(pwd->level_table[i]);
+       }
        free(fn->private_data);
        fn->private_data = NULL;
 }
@@ -1233,7 +1199,7 @@ next_buffer:
        out_size = WMA_OUTPUT_BUFFER_SIZE;
        out = para_malloc(out_size);
        ret = wma_decode_superframe(pwd, out, &out_size,
-               (uint8_t *)in + WMA_FRAME_SKIP, len - WMA_FRAME_SKIP);
+               (uint8_t *)in + WMA_FRAME_SKIP);
        if (ret < 0) {
                free(out);
                goto err;
@@ -1259,16 +1225,10 @@ static void wmadec_open(struct filter_node *fn)
        fn->min_iqs = 4096;
 }
 
-/**
- * The init function of the wma decoder.
- *
- * \param f Its fields are filled in by the function.
- */
-void wmadec_filter_init(struct filter *f)
-{
-       f->open = wmadec_open;
-       f->close = wmadec_close;
-       f->execute = wmadec_execute;
-       f->pre_select = generic_filter_pre_select;
-       f->post_select = wmadec_post_select;
-}
+const struct filter lsg_filter_cmd_com_wmadec_user_data = {
+       .open = wmadec_open,
+       .close = wmadec_close,
+       .execute = wmadec_execute,
+       .pre_select = generic_filter_pre_select,
+       .post_select = wmadec_post_select,
+};
diff --git a/write.c b/write.c
index 62caf09757ec52d45f825698d7f4656696d7bcff..acfb94605b53d259a89ea41e75507b81e2cb2ca4 100644 (file)
--- a/write.c
+++ b/write.c
@@ -1,24 +1,20 @@
-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file write.c Paraslash's standalone wav/raw player. */
 
 #include <regex.h>
 #include <sys/types.h>
+#include <lopsub.h>
 
+#include "write_cmd.lsg.h"
+#include "write.lsg.h"
 #include "para.h"
 #include "string.h"
-#include "write.cmdline.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "stdin.h"
 #include "buffer_tree.h"
 #include "write.h"
-#include "write_common.h"
 #include "fd.h"
 #include "error.h"
 #include "version.h"
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
-static struct write_args_info conf;
+#define CMD_PTR (lls_cmd(0, write_suite))
+#define OPT_RESULT(_name, _lpr) \
+       (lls_opt_result(LSG_WRITE_PARA_WRITE_OPT_ ## _name, _lpr))
+#define OPT_GIVEN(_name, _lpr) (lls_opt_given(OPT_RESULT(_name, _lpr)))
+#define OPT_UINT32_VAL(_name, _lpr) (lls_uint32_val(0, OPT_RESULT(_name, _lpr)))
 
 static struct stdin_task sit;
-
 static int loglevel;
 INIT_STDERR_LOGGING(loglevel)
 
-__noreturn static void print_help_and_die(void)
-{
-       struct ggo_help h = DEFINE_GGO_HELP(write);
-       bool d = conf.detailed_help_given;
-
-       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
-       print_writer_helps(d? GPH_MODULE_FLAGS_DETAILED : GPH_MODULE_FLAGS);
-       exit(0);
-}
-
-/*
- * Parse config and register a task for a writer node.
- *
- * \param arg Command line arguments.
- * \param parent The new node will be a child of \a parent.
- * \param wn The writer node.
- *
- * If arg is \p NULL, the OS-dependent default writer is used with no
- * arguments.  The default writers are alsa for Linux, osx for OS X, oss for
- * *BSD, and the file writer if the default writer is not supported.
- *
- * Once the writer configuration has been retrieved from the ->parse_config
- * callback a writer node is created, its buffer tree node is added to the
- * buffer tree as a child of the given parent.
- *
- * Finally, the new writer node's task structure is initialized and registered
- * to the paraslash scheduler.
- *
- * \return Standard.
- */
-static void setup_writer_node(const char *arg, struct btr_node *parent,
-               struct writer_node *wn, struct sched *s)
+static void handle_help_flag(struct lls_parse_result *lpr)
 {
-       wn->conf = check_writer_arg_or_die(arg, &wn->writer_num);
-       register_writer_node(wn, parent, s);
+       char *help;
+
+       if (OPT_GIVEN(DETAILED_HELP, lpr))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP, lpr))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       print_writer_helps(OPT_GIVEN(DETAILED_HELP, lpr));
+       exit(EXIT_SUCCESS);
 }
 
 struct write_task {
@@ -88,9 +66,9 @@ static int write_post_select(__a_unused struct sched *s, void *context)
        return check_wav_post_select(wt->cwc);
 }
 
-static int setup_and_schedule(void)
+static int setup_and_schedule(struct lls_parse_result *lpr)
 {
-       int i, ret;
+       int i, n, ret, writer_given = OPT_GIVEN(WRITER, lpr);
        struct btr_node *cw_btrn;
        struct writer_node *wns;
        static struct sched s;
@@ -101,7 +79,7 @@ static int setup_and_schedule(void)
                EMBRACE(.name = "stdin"));
        stdin_task_register(&sit, &s);
 
-       COPY_WAV_PARMS(&wp, &conf);
+       LLS_COPY_WAV_PARMS(&wp, LSG_WRITE_PARA_WRITE, lpr);
        wt.cwc = check_wav_init(sit.btrn, NULL, &wp, &cw_btrn);
        wt.task = task_register(&(struct task_info) {
                .name = "write",
@@ -109,28 +87,26 @@ static int setup_and_schedule(void)
                .post_select = write_post_select,
                .context = &wt,
        }, &s);
-       if (!conf.writer_given) {
-               wns = para_calloc(sizeof(*wns));
-               setup_writer_node(NULL, cw_btrn, wns, &s);
-               i = 1;
-       } else {
-               wns = para_calloc(conf.writer_given * sizeof(*wns));
-               for (i = 0; i < conf.writer_given; i++)
-                       setup_writer_node(conf.writer_arg[i], cw_btrn,
-                               wns + i, &s);
-       }
 
+       n = writer_given? writer_given : 1;
+       wns = para_calloc(n * sizeof(*wns));
+       for (i = 0; i < n; i++) {
+               const char *arg = i < writer_given?
+                       lls_string_val(i, OPT_RESULT(WRITER, lpr)) : NULL;
+               wns[i].wid = check_writer_arg_or_die(arg, &wns[i].lpr);
+               register_writer_node(wns + i, cw_btrn, &s);
+       }
        s.default_timeout.tv_sec = 10;
        s.default_timeout.tv_usec = 50000;
        ret = schedule(&s);
        if (ret >= 0) {
                int j, ts;
-               for (j = 0; j < i; j++) {
+               for (j = 0; j < n; j++) {
                        struct writer_node *wn = wns + j;
                        ts = task_status(wn->task);
                        assert(ts < 0);
                        if (ts != -E_WRITE_COMMON_EOF && ts != -E_BTR_EOF) {
-                               const char *name = writer_names[wn->writer_num];
+                               const char *name = writer_name(wn->wid);
                                PARA_ERROR_LOG("%s: %s\n", name,
                                        para_strerror(-ts));
                                if (ret >= 0)
@@ -138,14 +114,12 @@ static int setup_and_schedule(void)
                        }
                }
        }
-       for (i--; i >= 0; i--) {
+       for (i = n - 1; i >= 0; i--) {
                struct writer_node *wn = wns + i;
-               struct writer *w = writers + wn->writer_num;
-
-               w->close(wn);
+               writer_get(wn->wid)->close(wn);
                btr_remove_node(&wn->btrn);
-               w->free_config(wn->conf);
-               free(wn->conf);
+               lls_free_parse_result(wns[i].lpr,
+                       lls_cmd(wn->wid, write_cmd_suite));
        }
        free(wns);
        check_wav_shutdown(wt.cwc);
@@ -167,18 +141,23 @@ static int setup_and_schedule(void)
 int main(int argc, char *argv[])
 {
        int ret;
-
-       write_cmdline_parser(argc, argv, &conf);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       writer_init();
-       version_handle_flag("write", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-
-       ret = setup_and_schedule();
+       struct lls_parse_result *lpr;
+       char *errctx;
+
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL, lpr);
+       version_handle_flag("write",  OPT_GIVEN(VERSION, lpr));
+       handle_help_flag(lpr);
+       ret = setup_and_schedule(lpr);
+       lls_free_parse_result(lpr, CMD_PTR);
+out:
        if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-               exit(EXIT_FAILURE);
        }
-       exit(EXIT_SUCCESS);
+       return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
 }
diff --git a/write.h b/write.h
index c7c227eefb146db15160b70d8cb25618adb1f0b3..cb0beff812121b338e3749e80f63ad2cb99999db 100644 (file)
--- a/write.h
+++ b/write.h
@@ -1,24 +1,17 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file write.h Writer-related structures. */
 
-/** The list of supported writers. */
-enum writer_enum {WRITER_ENUM};
-
 /**
  * Describes one running instance of a writer.
  */
 struct writer_node {
-       /** The number of this writer. */
-       int writer_num;
+       /** The ID of this writer. */
+       int wid;
        /** Writer-specific data. */
        void *private_data;
-       /** The writer-specific configuration of this node. */
-       void *conf;
+       /** The parsed command line, merged with options given in the config file. */
+       struct lls_parse_result *lpr;
        /** The buffer tree node associated with this writer node. */
        struct btr_node *btrn;
        /** The task of this writer node. */
@@ -29,30 +22,6 @@ struct writer_node {
 
 /** Describes one supported writer. */
 struct writer {
-       /**
-        * The init function of the writer.
-        *
-        * It must fill in all other function pointers of the given
-        * writer structure.
-        */
-       void (*init)(struct writer *w);
-       /**
-        * The command line parser of the writer.
-        *
-        * It should check whether the command line options given by \a argv
-        * and \a argc are valid and return a pointer to the writer-specific
-        * configuration data determined by these options. This function must
-        * either succeed or call exit(). Note that parse_config_or_die() might
-        * be called more than once with different values of \a options. \sa
-        * \ref free_config().
-        */
-       void *(*parse_config_or_die)(int argc, char **argv);
-       /**
-        * Deallocate all configuration resources.
-        *
-        * This should free whatever was allocated by \ref parse_config_or_die().
-        */
-       void (*free_config)(void *config);
        /**
         * Prepare the fd sets for select.
         *
@@ -72,8 +41,6 @@ struct writer {
         * This function is assumed to succeed.
         */
        void (*close)(struct writer_node *);
-       /** The short and the log help text of this writer. */
-       struct ggo_help help;
        /**
         * The callback handler.
         *
@@ -83,14 +50,23 @@ struct writer {
        btr_command_handler execute;
 };
 
-/** Loop over each supported writer. */
-#define FOR_EACH_WRITER(i) for (i = 0; i < NUM_SUPPORTED_WRITERS; i++)
-
-/** Declare the init functions of all supported writers. */
-DECLARE_WRITER_INITS;
+#define WRITE_CMD(_num) (lls_cmd(_num, write_cmd_suite))
 
-/** Array containing the name of each writer. */
-extern const char *writer_names[];
+#define WRITE_CMD_OPT_RESULT(_cmd, _opt, _lpr) \
+       (lls_opt_result(LSG_WRITE_CMD_ ## _cmd ## _OPT_ ## _opt, _lpr))
+#define WRITE_CMD_OPT_GIVEN(_cmd, _opt, _lpr) \
+       (lls_opt_given(WRITE_CMD_OPT_RESULT(_cmd, _opt, _lpr)))
+#define WRITE_CMD_OPT_UINT32_VAL(_cmd, _opt, _lpr) \
+       (lls_uint32_val(0, WRITE_CMD_OPT_RESULT(_cmd, _opt, (_lpr))))
+#define WRITE_CMD_OPT_STRING_VAL(_cmd, _opt, _lpr) \
+       (lls_string_val(0, WRITE_CMD_OPT_RESULT(_cmd, _opt, (_lpr))))
 
-/** The writer structure for each supported writer. */
-extern struct writer writers[NUM_SUPPORTED_WRITERS];
+int check_writer_arg_or_die(const char *wa, struct lls_parse_result **lprp);
+const struct writer *writer_get(int wid);
+const char *writer_name(int wid);
+void register_writer_node(struct writer_node *wn, struct btr_node *parent,
+               struct sched *s);
+void get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
+void get_btr_channels(struct btr_node *btrn, int32_t *result);
+void get_btr_sample_format(struct btr_node *btrn, int32_t *result);
+void print_writer_helps(bool detailed);
index cdb67e58fd65bf4438522fd0bed4ef9092aaaeaf..14cc98a4189a5f74907f8bfcf416f62bcc2515c5 100644 (file)
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file write_common.c common functions of para_audiod and para_write */
 
 #include <regex.h>
+#include <lopsub.h>
 
+#include "write_cmd.lsg.h"
 #include "para.h"
 #include "string.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "write.h"
 #include "error.h"
-#include "write_common.h"
 
-/** the array containing the names of all supported writers */
-const char *writer_names[] ={WRITER_NAMES};
+/** Loop over all writers. */
+#define FOR_EACH_WRITER(i) for (i = 1; lls_cmd(i, write_cmd_suite); i++)
 
-/** the array of supported writers */
-struct writer writers[NUM_SUPPORTED_WRITERS] = {WRITER_ARRAY};
 
-/**
- * Call the init function of each supported paraslash writer.
- */
-void writer_init(void)
+static inline bool writer_supported(int wid)
+{
+       return lls_user_data(WRITE_CMD(wid));
+}
+
+/* simply return the first available writer */
+static int default_writer_id(void)
 {
        int i;
 
        FOR_EACH_WRITER(i)
-               writers[i].init(&writers[i]);
+               if (writer_supported(i))
+                       return i;
+       assert(0); /* the file writer should always be available */
 }
+
 /**
- * Check if given string is a valid command line for any writer.
+ * Return the writer structure from a writer ID.
  *
- * \param \wa String of the form writer_name:options.
- * \param writer_num Contains the number of the writer upon success.
+ * \param wid If non-positive, a pointer to the default writer is returned.
+ *
+ * \return Pointer to a (constant) struct writer.
+ */
+const struct writer *writer_get(int wid)
+{
+       if (wid < 0)
+               wid = default_writer_id();
+       return lls_user_data(WRITE_CMD(wid));
+}
+
+/**
+ * Return name of the writer identified by a writer ID.
  *
- * This function checks whether \a wa starts with the name of a supported
- * paraslash writer, optionally followed by a colon and any options for that
- * writer.  If a valid writer name was found and further are present, the
- * remaining part of \a wa is passed to that writer's config parser.
+ * \param wid If non-positive, the name of the default writer is returned.
  *
- * \return On success, a pointer to the gengetopt args info struct is returned
- * and \a writer_num contains the number of the writer. Otherwise this function
- * prints an error message and calls exit().
+ * \return The returned buffer must not be freed by the caller.
  */
-void *check_writer_arg_or_die(const char *wa, int *writer_num)
+const char *writer_name(int wid)
 {
-       int i, ret, argc;
-       const char *cmdline;
-       char **argv;
-       void *conf;
+       if (wid <= 0)
+               wid = default_writer_id();
+       return lls_command_name(WRITE_CMD(wid));
+}
 
-       if (!wa || !*wa) {
-               i = DEFAULT_WRITER;
-               cmdline = NULL;
-               goto check;
-       }
-       PARA_INFO_LOG("checking %s\n", wa);
-       FOR_EACH_WRITER(i) {
-               const char *name = writer_names[i];
-               size_t len = strlen(name);
-               char c;
+/**
+ * Check if the given string is a valid command line for any writer.
+ *
+ * \param wa String of the form writer_name options.
+ * \param lprp Contains the parsed command line on success.
+ *
+ * If wa is \p NULL, the (configuration-dependent) default writer is assumed.
+ * Otherwise, the function checks whether \a wa starts with the name of a
+ * supported writer. If a valid writer name was found, the rest of the command
+ * line is passed to the config parser of this writer.
+ *
+ * \return On success, the positive writer ID is returned. Otherwise the
+ * function prints an error message and calls exit().
+ */
+int check_writer_arg_or_die(const char *wa, struct lls_parse_result **lprp)
+{
+       int ret, writer_num, argc;
+       char **argv = NULL, *errctx = NULL;
+       const struct lls_command *cmd;
 
-               if (strlen(wa) < len)
-                       continue;
-               if (strncmp(name, wa, len))
-                       continue;
-               c = wa[len];
-               if (!c || c == ' ') {
-                       cmdline = c? wa + len + 1 : NULL;
-                       goto check;
-               }
-       }
-       PARA_EMERG_LOG("invalid writer %s\n", wa);
-       exit(EXIT_FAILURE);
-check:
-       ret = create_shifted_argv(cmdline, " \t", &argv);
-       if (ret < 0) {
-               PARA_EMERG_LOG("%s: %s\n", wa, para_strerror(-ret));
-               exit(EXIT_FAILURE);
+       if (!wa || !*wa) {
+               writer_num = default_writer_id();
+               cmd = WRITE_CMD(writer_num);
+               argv = para_malloc(2 * sizeof(char *));
+               argc = 1;
+               argv[0] = para_strdup(lls_command_name(cmd));
+               argv[1] = NULL;
+               goto parse;
        }
+       ret = create_argv(wa, " \t\n", &argv);
+       if (ret < 0)
+               goto fail;
        argc = ret;
-       argv[0] = make_message("%s_write", writer_names[i]);
-       *writer_num = i;
-       conf = writers[i].parse_config_or_die(argc, argv);
+       ret = lls(lls_lookup_subcmd(argv[0], write_cmd_suite, &errctx));
+       if (ret < 0)
+               goto free_argv;
+       writer_num = ret;
+       cmd = WRITE_CMD(writer_num);
+       if (!writer_supported(writer_num)) {
+               ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+               errctx = make_message("%s writer is not supported",
+                       lls_command_name(cmd));
+               goto free_argv;
+       }
+parse:
+       ret = lls(lls_parse(argc, argv, cmd, lprp, &errctx));
+       if (ret >= 0)
+               ret = writer_num;
+free_argv:
        free_argv(argv);
-       return conf;
+       if (ret >= 0)
+               return ret;
+fail:
+       if (errctx)
+               PARA_ERROR_LOG("%s\n", errctx);
+       free(errctx);
+       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       exit(EXIT_FAILURE);
 }
 
 /**
@@ -99,20 +128,17 @@ check:
  * \param wn The writer node to open.
  * \param parent The parent btr node (the source for the writer node).
  * \param s The scheduler instance to register the task to.
- *
- * The configuration of the writer node stored in \p wn->conf must be
- * initialized before this function may be called.
  */
 void register_writer_node(struct writer_node *wn, struct btr_node *parent,
                struct sched *s)
 {
-       struct writer *w = writers + wn->writer_num;
+       const struct writer *w = writer_get(wn->wid);
 
        wn->btrn = btr_new_node(&(struct btr_node_description)
-               EMBRACE(.name = writer_names[wn->writer_num], .parent = parent,
+               EMBRACE(.name = writer_name(wn->wid), .parent = parent,
                .handler = w->execute, .context = wn));
        wn->task = task_register(&(struct task_info) {
-               .name = writer_names[wn->writer_num],
+               .name = writer_name(wn->wid),
                .pre_select = w->pre_select,
                .post_select = w->post_select,
                .context = wn,
@@ -122,24 +148,29 @@ void register_writer_node(struct writer_node *wn, struct btr_node *parent,
 /**
  * Print the help text of all writers to stdout.
  *
- * \param flags Passed to \ref ggo_print_help().
+ * \param detailed Whether to print the short or the detailed help.
  */
-void print_writer_helps(unsigned flags)
+void print_writer_helps(bool detailed)
 {
        int i;
 
-       printf_or_die("\nAvailable writers: ");
-       FOR_EACH_WRITER(i)
-               printf_or_die("%s%s", i? " " : "", writer_names[i]);
-       printf_or_die("\n");
+       printf("\nAvailable writers: ");
        FOR_EACH_WRITER(i) {
-               struct writer *w = writers + i;
-
-               if (!w->help.short_help)
+               if (!writer_supported(i))
+                       continue;
+               printf("%s%s", i? " " : "", writer_name(i));
+       }
+       printf("\n");
+       FOR_EACH_WRITER(i) {
+               const struct lls_command *cmd = WRITE_CMD(i);
+               char *help;
+               if (!writer_supported(i))
+                       continue;
+               help = detailed? lls_long_help(cmd) : lls_short_help(cmd);
+               if (!help)
                        continue;
-               printf_or_die("\n%s: %s", writer_names[i],
-                       w->help.purpose);
-               ggo_print_help(&w->help, flags);
+               printf("%s\n", help);
+               free(help);
        }
 }
 
diff --git a/write_common.h b/write_common.h
deleted file mode 100644 (file)
index 1828875..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file write_common.h Exported symbols from write_common.c. */
-
-void writer_init(void);
-void *check_writer_arg_or_die(const char *wa, int *writer_num);
-void print_writer_helps(unsigned flags);
-void register_writer_node(struct writer_node *wn, struct btr_node *parent,
-               struct sched *s);
-void get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
-void get_btr_channels(struct btr_node *btrn, int32_t *result);
-void get_btr_sample_format(struct btr_node *btrn, int32_t *result);
diff --git a/yy/makefile b/yy/makefile
new file mode 100644 (file)
index 0000000..ed70d65
--- /dev/null
@@ -0,0 +1,17 @@
+.PRECIOUS: $(yy_build_dir)/%.flex.c $(yy_build_dir)/%.bison.c \
+       $(yy_build_dir)/%.bison.h
+
+$(yy_build_dir)/%.flex.c: $(yy_src_dir)/%.lex | $(yy_build_dir)
+       @[ -z "$(Q)" ] || echo 'FLEX $<'
+       @$(FLEX) -o $@ $<
+
+$(yy_build_dir)/%.bison.c $(yy_build_dir)/%.bison.h: $(yy_src_dir)/%.y \
+               | $(yy_build_dir)
+       @[ -z "$(Q)" ] || echo 'BISON $<'
+       @$(BISON) --defines=$(yy_build_dir)/$(notdir $(<:.y=.bison.h)) \
+               --output=$(yy_build_dir)/$(notdir $(<:.y=.bison.c)) $<
+
+$(object_dir)/%.o: $(yy_build_dir)/%.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       @$(Q) $(CC) -g -c -o $@ $(CPPFLAGS) -MMD -MF $(dep_dir)/$(*F).d \
+               -MT $@ -iquote . -Wno-unused-macros $<
diff --git a/yy/mp.lex b/yy/mp.lex
new file mode 100644 (file)
index 0000000..2dbe21b
--- /dev/null
+++ b/yy/mp.lex
@@ -0,0 +1,137 @@
+/* Copyright (C) 2017 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+ /*
+  * Since we do not supply yywrap(), we use noyywrap to instruct the scanner to
+  * behave as though yywrap() returned 1.
+  */
+%option noyywrap
+
+ /*
+  * We don't want symbols to clash with those of other flex users, particularly
+  * lopsub.
+  */
+%option prefix="mp_yy"
+
+ /*
+  * Generate a scanner that maintains the number of the current line read from
+  * its input in the yylineno variable.
+  */
+%option yylineno
+
+ /* Generate a bison-compatible scanner. */
+%option bison-bridge bison-locations
+
+ /*
+  * Warn (in particular) if the default rule can be matched but no default rule
+  * has been given.
+  */
+%option warn
+
+ /*
+  * Generate a scanner which is portable and safe to use in one or more threads
+  * of control.
+  */
+%option reentrant
+
+ /*
+  * Generate a scanner which always looks one extra character ahead. This is a
+  * bit faster than an interactive scanner for which look ahead happens only
+  * when necessary.
+  */
+%option never-interactive
+
+%{
+#include <regex.h>
+#include "para.h"
+#include "string.h"
+#include "mp.h"
+#include "error.h"
+
+#define YYSTYPE MP_YYSTYPE
+#define YYLTYPE MP_YYLTYPE
+#define YY_DECL int mp_yylex(MP_YYSTYPE *yylval_param, MP_YYLTYPE *yylloc_param, \
+       struct mp_context *ctx, struct mp_ast_node **ast, mp_yyscan_t yyscanner)
+#include "mp.bison.h"
+#define MP_YY_USER_ACTION do {mp_yylloc->first_line = mp_yylineno;} while (0);
+%}
+DECIMAL_CONSTANT  (0|([[:digit:]]{-}[0])[[:digit:]]*)
+STRING_LITERAL    \"([^\"\\\n]|(\\[\"\\abfnrtv]))*\"
+REGEX_PATTERN     \/([^\/\\\n]|(\\[\/\\abfnrtv]))*\/([in])*
+WILDCARD_PATTERN  \|([^\|\\\n]|(\\[\|\\abfnrtv]))*\|([npPlie])*
+%%
+
+is_set {return IS_SET;}
+num_attributes_set {return NUM_ATTRIBUTES_SET;}
+path {return PATH;}
+artist {return ARTIST;}
+title {return TITLE;}
+album {return ALBUM;}
+comment {return COMMENT;}
+year {return YEAR;}
+num_played {return NUM_PLAYED;}
+image_id {return IMAGE_ID;}
+lyrics_id {return LYRICS_ID;}
+bitrate {return BITRATE;}
+frequency {return FREQUENCY;}
+channels {return CHANNELS;}
+true {return TRUE;}
+false {return FALSE;}
+
+[[:space:]]+|#.*\n /* skip comments and whitespace */
+
+"("|")"|","|"+"|"-"|"*"|"/"|"<"|">" {return yytext[0];}
+
+"||" {return OR;}
+"&&" {return AND;}
+"!" {return NOT;}
+"==" {return EQUAL;}
+"!=" {return NOT_EQUAL;}
+"<=" {return LESS_OR_EQUAL;}
+">=" {return GREATER_OR_EQUAL;}
+"=~" {return REGEX_MATCH;}
+"=|" {return FILENAME_MATCH;}
+
+{DECIMAL_CONSTANT} {
+       int ret;
+       yylval->node = mp_new_ast_leaf_node(NUM);
+       ret = para_atoi64(yytext, &yylval->node->sv.intval);
+       if (ret < 0) {
+               free(yylval->node);
+               mp_parse_error(yylloc->first_line, ctx, "%s: %s", yytext,
+                       para_strerror(-ret));
+               return -E_MOOD_PARSE;
+       }
+       return NUM;
+}
+
+{STRING_LITERAL} {
+       yylval->node = mp_new_ast_leaf_node(STRING_LITERAL);
+       parse_quoted_string(yytext, "\"\"", &yylval->node->sv.strval);
+       //PARA_CRIT_LOG("strval: %s\n", yylval->node->sv.strval);
+       //PARA_CRIT_LOG("node: %p\n", yylval->node);
+       return STRING_LITERAL;
+}
+
+{REGEX_PATTERN} {
+       int ret;
+       yylval->node = mp_new_ast_leaf_node(REGEX_PATTERN);
+       ret = mp_parse_regex_pattern(yytext, &yylval->node->sv.re_pattern);
+       if (ret < 0) {
+               mp_parse_error(yylloc->first_line, ctx, "%s: %s", yytext,
+                       para_strerror(-ret));
+               return -E_MOOD_PARSE;
+       }
+       return REGEX_PATTERN;
+}
+
+{WILDCARD_PATTERN} {
+       yylval->node = mp_new_ast_leaf_node(WILDCARD_PATTERN);
+       mp_parse_wildcard_pattern(yytext, &yylval->node->sv.wc_pattern);
+       return WILDCARD_PATTERN;
+}
+
+. {
+       mp_parse_error(yylloc->first_line, ctx, "unrecognized text: %s",
+               yytext);
+       return -E_MOOD_PARSE;
+}
diff --git a/yy/mp.y b/yy/mp.y
new file mode 100644 (file)
index 0000000..0f2c9cb
--- /dev/null
+++ b/yy/mp.y
@@ -0,0 +1,415 @@
+/* Copyright (C) 2017 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/*
+ * Provide more verbose and specific error messages instead of just "syntax
+ * error".
+ */
+%define parse.error verbose
+
+/*
+ * Verbose error messages may contain incorrect information if LAC (Lookahead
+ * Correction) is not enabled.
+ */
+%define parse.lac full
+
+/* Avoid symbol clashes (lopsub might also expose yy* symbols). */
+%define api.prefix {mp_yy}
+
+/*
+ * Although locations are automatically enabled as soon as the grammar uses the
+ * special @N tokens, specifying %locations explicitly allows for more accurate
+ * syntax error messages.
+ */
+%locations
+
+/*
+ * Generate a pure (reentrant) parser. With this option enabled, yylval and
+ * yylloc become local variables in yyparse(), and a different calling
+ * convention is used for yylex().
+ */
+%define api.pure full
+
+/* Additional arguments to yylex(), yyparse() and yyerror() */
+%param {struct mp_context *ctx}
+%param {struct mp_ast_node **ast}
+%param {mp_yyscan_t yyscanner} /* reentrant lexers */
+
+%{
+#include <fnmatch.h>
+#include <regex.h>
+
+#include "para.h"
+#include "string.h"
+#include "mp.h"
+#include "mp.bison.h"
+#include "error.h"
+
+int yylex(MP_YYSTYPE *lvalp, MP_YYLTYPE *llocp, struct mp_context *ctx,
+               struct mp_ast_node **ast, mp_yyscan_t yyscanner);
+static void yyerror(YYLTYPE *llocp, struct mp_context *ctx,
+               struct mp_ast_node **ast, mp_yyscan_t yyscanner, const char *msg);
+
+enum semantic_types {
+       ST_STRVAL,
+       ST_INTVAL,
+       ST_BOOLVAL,
+       ST_REGEX_PATTERN,
+       ST_WC_PATTERN
+};
+
+static struct mp_ast_node *ast_node_raw(int id)
+{
+       struct mp_ast_node *node = para_malloc(sizeof(struct mp_ast_node));
+       node->id = id;
+       return node;
+}
+
+/* This is non-static because it is also called from the lexer. */
+struct mp_ast_node *mp_new_ast_leaf_node(int id)
+{
+       struct mp_ast_node *node = ast_node_raw(id);
+       node->num_children = 0;
+       return node;
+}
+
+static struct mp_ast_node *ast_node_new_unary(int id, struct mp_ast_node *child)
+{
+       struct mp_ast_node *node = ast_node_raw(id);
+       node->num_children = 1;
+       node->children = para_malloc(sizeof(struct mp_ast_node *));
+       node->children[0] = child;
+       return node;
+}
+
+static struct mp_ast_node *ast_node_new_binary(int id, struct mp_ast_node *left,
+               struct mp_ast_node *right)
+{
+       struct mp_ast_node *node = ast_node_raw(id);
+       node->num_children = 2;
+       node->children = para_malloc(2 * sizeof(struct mp_ast_node *));
+       node->children[0] = left;
+       node->children[1] = right;
+       return node;
+}
+
+void mp_free_ast(struct mp_ast_node *root)
+{
+       if (!root)
+               return;
+       if (root->num_children > 0) {
+               int i;
+               for (i = 0; i < root->num_children; i++)
+                       mp_free_ast(root->children[i]);
+               free(root->children);
+       } else {
+               union mp_semantic_value *sv = &root->sv;
+               switch (root->id) {
+               case STRING_LITERAL:
+                       free(sv->strval);
+                       break;
+               case REGEX_PATTERN:
+                       regfree(&sv->re_pattern.preg);
+                       break;
+               case WILDCARD_PATTERN:
+                       free(sv->wc_pattern.pat);
+                       break;
+               }
+       }
+       free(root);
+}
+
+static int eval_node(struct mp_ast_node *node, struct mp_context *ctx,
+               union mp_semantic_value *result);
+
+static void eval_binary_op(struct mp_ast_node *node, struct mp_context *ctx,
+               union mp_semantic_value *v1, union mp_semantic_value *v2)
+{
+       eval_node(node->children[0], ctx, v1);
+       eval_node(node->children[1], ctx, v2);
+}
+
+static int eval_node(struct mp_ast_node *node, struct mp_context *ctx,
+               union mp_semantic_value *result)
+{
+       int ret;
+       char *arg;
+       union mp_semantic_value v1, v2;
+
+       switch (node->id) {
+       /* strings */
+       case STRING_LITERAL:
+               result->strval = node->sv.strval;
+               return ST_STRVAL;
+       case PATH:
+               result->strval = mp_path(ctx);
+               return ST_STRVAL;
+       case ARTIST:
+               result->strval = mp_artist(ctx);
+               return ST_STRVAL;
+       case TITLE:
+               result->strval = mp_title(ctx);
+               return ST_STRVAL;
+       case ALBUM:
+               result->strval = mp_album(ctx);
+               return ST_STRVAL;
+       case COMMENT:
+               result->strval = mp_comment(ctx);
+               return ST_STRVAL;
+       /* integers */
+       case NUM:
+               result->intval = node->sv.intval;
+               return ST_INTVAL;
+       case '+':
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->intval = v1.intval + v2.intval;
+               return ST_INTVAL;
+       case '-':
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->intval = v1.intval - v2.intval;
+               return ST_INTVAL;
+       case '*':
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->intval = v1.intval * v2.intval;
+               return ST_INTVAL;
+       case '/':
+               eval_binary_op(node, ctx, &v1, &v2);
+               if (v2.intval == 0) {
+                       static bool warned;
+                       if (!warned)
+                               PARA_ERROR_LOG("division by zero\n");
+                       warned = true;
+                       result->intval = 0;
+               } else
+                       result->intval = v1.intval / v2.intval;
+               return ST_INTVAL;
+       case NEG:
+               eval_node(node->children[0], ctx, &v1);
+               result->intval = -v1.intval;
+               return ST_INTVAL;
+       case YEAR:
+               result->intval = mp_year(ctx);
+               return ST_INTVAL;
+       case NUM_ATTRIBUTES_SET:
+               result->intval = mp_num_attributes_set(ctx);
+               return ST_INTVAL;
+       case NUM_PLAYED:
+               result->intval = mp_num_played(ctx);
+               return ST_INTVAL;
+       case IMAGE_ID:
+               result->intval = mp_image_id(ctx);
+               return ST_INTVAL;
+       case LYRICS_ID:
+               result->intval = mp_lyrics_id(ctx);
+               return ST_INTVAL;
+       case BITRATE:
+               result->intval = mp_bitrate(ctx);
+               return ST_INTVAL;
+       case FREQUENCY:
+               result->intval = mp_frequency(ctx);
+               return ST_INTVAL;
+       case CHANNELS:
+               result->intval= mp_channels(ctx);
+               return ST_INTVAL;
+       /* bools */
+       case IS_SET:
+               arg = node->children[0]->sv.strval;
+               result->boolval = mp_is_set(arg, ctx);
+               return ST_BOOLVAL;
+       case TRUE:
+               result->boolval = true;
+               return ST_BOOLVAL;
+       case FALSE:
+               result->boolval = false;
+               return ST_BOOLVAL;
+       case OR:
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->boolval = v1.boolval || v2.boolval;
+               return ST_BOOLVAL;
+       case AND:
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->boolval = v1.boolval && v2.boolval;
+               return ST_BOOLVAL;
+       case NOT:
+               eval_node(node->children[0], ctx, &v1);
+               result->boolval = !v1.boolval;
+               return ST_BOOLVAL;
+       case EQUAL:
+               ret = eval_node(node->children[0], ctx, &v1);
+               eval_node(node->children[1], ctx, &v2);
+               if (ret == ST_STRVAL)
+                       result->boolval = !strcmp(v1.strval, v2.strval);
+               else
+                       result->boolval = v1.intval == v2.intval;
+               return ST_BOOLVAL;
+       case NOT_EQUAL:
+               ret = eval_node(node->children[0], ctx, &v1);
+               eval_node(node->children[1], ctx, &v2);
+               if (ret == ST_STRVAL)
+                       result->boolval = strcmp(v1.strval, v2.strval);
+               else
+                       result->boolval = v1.intval != v2.intval;
+               return ST_BOOLVAL;
+       case '<':
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->boolval = v1.intval < v2.intval;
+               return ST_BOOLVAL;
+       case '>':
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->boolval = v1.intval > v2.intval;
+               return ST_BOOLVAL;
+       case LESS_OR_EQUAL:
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->boolval = v1.intval <= v2.intval;
+               return ST_BOOLVAL;
+       case GREATER_OR_EQUAL:
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->boolval = v1.intval >= v2.intval;
+               return ST_BOOLVAL;
+       case FILENAME_MATCH:
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->boolval = fnmatch(v2.wc_pattern.pat, v1.strval,
+                       v2.wc_pattern.flags) == 0;
+               return ST_BOOLVAL;
+       case REGEX_MATCH:
+               eval_binary_op(node, ctx, &v1, &v2);
+               result->boolval = regexec(&v2.re_pattern.preg, v1.strval,
+                        0, NULL, 0) == 0;
+               return ST_BOOLVAL;
+       case REGEX_PATTERN:
+               result->re_pattern = node->sv.re_pattern;
+               return ST_REGEX_PATTERN;
+       case WILDCARD_PATTERN:
+               result->wc_pattern = node->sv.wc_pattern;
+               return ST_WC_PATTERN;
+       default:
+               PARA_EMERG_LOG("bug: invalid node id %d\n", node->id);
+               exit(EXIT_FAILURE);
+       }
+}
+
+bool mp_eval_ast(struct mp_ast_node *root, struct mp_context *ctx)
+{
+       union mp_semantic_value v;
+       int ret = eval_node(root, ctx, &v);
+
+       if (ret == ST_INTVAL)
+               return v.intval != 0;
+       if (ret == ST_STRVAL)
+               return v.strval[0] != 0;
+       if (ret == ST_BOOLVAL)
+               return v.boolval;
+       assert(false);
+}
+
+%}
+
+%union {
+       struct mp_ast_node *node;
+}
+
+/* terminals */
+%token <node> NUM
+%token <node> STRING_LITERAL
+%token <node> REGEX_PATTERN
+%token <node> WILDCARD_PATTERN
+
+/* keywords with semantic value */
+%token <node> PATH
+%token <node> ARTIST
+%token <node> TITLE
+%token <node> ALBUM
+%token <node> COMMENT
+%token <node> YEAR
+%token <node> NUM_ATTRIBUTES_SET
+%token <node> NUM_PLAYED
+%token <node> IMAGE_ID
+%token <node> LYRICS_ID
+%token <node> BITRATE
+%token <node> FREQUENCY
+%token <node> CHANNELS
+%token <node> FALSE TRUE
+
+/* keywords without semantic value */
+%token IS_SET
+
+/* operators, ordered by precendence */
+%left OR
+%left AND
+%left EQUAL NOT_EQUAL
+%left LESS_THAN LESS_OR_EQUAL GREATER_OR_EQUAL REGEX_MATCH FILENAME_MATCH
+%left '-' '+'
+%left '*' '/'
+%right NOT NEG /* negation (unary minus) */
+
+/* nonterminals */
+%type <node> string
+%type <node> exp
+%type <node> boolexp
+
+%%
+
+program:
+        /* empty */ {*ast = NULL; return 0;}
+        | string {*ast = $1; return 0;}
+        | exp {*ast = $1; return 0;}
+       | boolexp {*ast = $1; return 0;}
+
+string: STRING_LITERAL {$$ = $1;}
+       | PATH {$$ = mp_new_ast_leaf_node(PATH);}
+       | ARTIST {$$ = mp_new_ast_leaf_node(ARTIST);}
+       | TITLE {$$ = mp_new_ast_leaf_node(TITLE);}
+       | ALBUM {$$ = mp_new_ast_leaf_node(ALBUM);}
+       | COMMENT {$$ = mp_new_ast_leaf_node(COMMENT);}
+;
+
+exp: NUM {$$ = $1;}
+        | exp '+' exp {$$ = ast_node_new_binary('+', $1, $3);}
+        | exp '-' exp {$$ = ast_node_new_binary('-', $1, $3);}
+        | exp '*' exp {$$ = ast_node_new_binary('*', $1, $3);}
+        | exp '/' exp {$$ = ast_node_new_binary('/', $1, $3);}
+        | '-' exp %prec NEG {$$ = ast_node_new_unary(NEG, $2);}
+        | '(' exp ')' {$$ = $2;}
+       | YEAR {$$ = mp_new_ast_leaf_node(YEAR);}
+       | NUM_ATTRIBUTES_SET {$$ = mp_new_ast_leaf_node(NUM_ATTRIBUTES_SET);}
+       | NUM_PLAYED {$$ = mp_new_ast_leaf_node(NUM_PLAYED);}
+       | IMAGE_ID {$$ = mp_new_ast_leaf_node(IMAGE_ID);}
+       | LYRICS_ID {$$ = mp_new_ast_leaf_node(LYRICS_ID);}
+       | BITRATE {$$ = mp_new_ast_leaf_node(BITRATE);}
+       | FREQUENCY {$$ = mp_new_ast_leaf_node(FREQUENCY);}
+       | CHANNELS {$$ = mp_new_ast_leaf_node(CHANNELS);}
+;
+
+boolexp: IS_SET '(' STRING_LITERAL ')' {$$ = ast_node_new_unary(IS_SET, $3);}
+       | TRUE {$$ = mp_new_ast_leaf_node(TRUE);}
+       | FALSE {$$ = mp_new_ast_leaf_node(FALSE);}
+       | '(' boolexp ')' {$$ = $2;}
+       | boolexp OR boolexp {$$ = ast_node_new_binary(OR, $1, $3);}
+       | boolexp AND boolexp {$$ = ast_node_new_binary(AND, $1, $3);}
+       | NOT boolexp {$$ = ast_node_new_unary(NOT, $2);}
+       | exp EQUAL exp {$$ = ast_node_new_binary(EQUAL, $1, $3);}
+       | exp NOT_EQUAL exp {$$ = ast_node_new_binary(NOT_EQUAL, $1, $3);}
+       | exp '<' exp {$$ = ast_node_new_binary('<', $1, $3);}
+       | exp '>' exp {$$ = ast_node_new_binary('>', $1, $3);}
+       | exp LESS_OR_EQUAL exp {
+               $$ = ast_node_new_binary(LESS_OR_EQUAL, $1, $3);
+       }
+       | exp GREATER_OR_EQUAL exp {
+               $$ = ast_node_new_binary(GREATER_OR_EQUAL, $1, $3);
+       }
+       | string REGEX_MATCH REGEX_PATTERN {
+               $$ = ast_node_new_binary(REGEX_MATCH, $1, $3);
+       }
+       | string FILENAME_MATCH WILDCARD_PATTERN {
+               $$ = ast_node_new_binary(FILENAME_MATCH, $1, $3);
+       }
+       | string EQUAL string {$$ = ast_node_new_binary(EQUAL, $1, $3);}
+       | string NOT_EQUAL string {$$ = ast_node_new_binary(NOT_EQUAL, $1, $3);}
+;
+%%
+
+/* Called by yyparse() on error */
+static void yyerror(YYLTYPE *llocp, struct mp_context *ctx,
+               struct mp_ast_node **ast, mp_yyscan_t yyscanner, const char *msg)
+{
+       mp_parse_error(llocp->first_line, ctx, "%s", msg);
+}