]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 'refs/heads/t/lopsub'
authorAndre Noll <maan@tuebingen.mpg.de>
Thu, 27 Apr 2017 18:39:32 +0000 (20:39 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Thu, 27 Apr 2017 18:47:05 +0000 (20:47 +0200)
The bulk of the changes in this release is the conversion of all
command line parsers from gengetopt to lopsub.

The series also contains a few cleanups that have become possible
due to the switch from gengetopt to lopsub.

The patches towards the end of the series rename para_fade to
para_mixer.

Naturally, the merge conflicted rather heavily against the other
topic branches that have been merged since the lopsub branch was
started. Conflicting files:

Makefile.real afh.c afh_recv.c configure.ac osx_write.c write.c

The resolutions for these conflicts were recorded with git rerere
and have been tested for quite some time.

Cooking for three weeks.

* refs/heads/t/lopsub: (74 commits)
  audioc: Avoid double free in audioc_i9e_line_handler().
  audiod: Avoid uninitialized memory access.
  Simplify mixer setup.
  mixer: Implement non-linear time scale for fading.
  mixer: Allow arbitrary relative time for sleep subcommand.
  Convert para_fade to subcommands, rename it to para_mixer.
  build: Create .dep files only during compilation.
  build: Simplify definition of $m4_lls_deps.
  build: Rename command list variables.
  build: Combine $(CFLAGS) and $(STRICT_CFLAGS).
  build: Let .d files depend only on .c.
  build: Don't create phony targets for dependencies.
  build: Remove duplicate dependency.
  build: Remove cmdline_dir and friends.
  build: Remove some unused variables from Makefile.real.
  build: Remove m4/gengetopt.
  Remove gengetopt and help2man checks from configure.ac.
  Remove man_util.bash.
  Remove ggo.c and ggo.h.
  manual: Do not mention gengetopt and help2man any more.
  ...

18 files changed:
1  2 
INSTALL
Makefile.in
Makefile.real
NEWS.md
aacdec_filter.c
afh.c
afh_recv.c
afs.c
aft.c
audiod.c
client_common.c
command.c
configure.ac
error.h
server.h
vss.c
web/manual.md
wmadec_filter.c

diff --combined INSTALL
index 547036b104d03759d79626e6ac3b686b165b88c0,d0e8a7908cba07e2fbd5a2138d6fef3599c4982f..4a86e967ee72955a32ed37e2fd88b849f808053e
+++ b/INSTALL
@@@ -1,5 -1,11 +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
@@@ -16,8 -22,10 +22,8 @@@ Installing paraslash from gi
  
  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
@@@ -27,7 -35,7 +33,7 @@@
        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:
  
diff --combined Makefile.in
index aff321fd95d8d5f5114ccd7abe513451e3b09061,e1f7634efa001c52221333dad6773c99308c3a25..b3e3be5cc82deb414f85404af830759ccd0e0963
@@@ -8,16 -8,10 +8,9 @@@ datarootdir := @datarootdir
  PACKAGE_TARNAME := @PACKAGE_TARNAME@
  PACKAGE_VERSION := @PACKAGE_VERSION@
  
 -INSTALL := @INSTALL@
  M4 := @M4@
- GENGETOPT := @GENGETOPT@
- HELP2MAN := @HELP2MAN@
- ggo_descriptions_declared := @ggo_descriptions_declared@
  
  executables := @executables@
- receivers := @receivers@
- filters := @filters@
- writers := @writers@
  
  recv_objs := @recv_objs@
  filter_objs := @filter_objs@
@@@ -25,7 -19,7 +18,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@
@@@ -38,6 -32,7 +31,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@
@@@ -49,7 -44,9 +43,7 @@@ samplerate_cppflags := @samplerate_cppf
  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@
@@@ -58,6 -55,7 +52,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@
@@@ -66,7 -64,9 +61,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
diff --combined Makefile.real
index 29c4bae7e8e593debbe45afd62e143b66c75c218,a2e6a423dd6624c4538954637feca67bbe9dbc44..3631a5c9f47569c48d38df3877cea286a12e468b
@@@ -7,8 -7,10 +7,9 @@@ ifeq ("$(origin CC)", "default"
          CC := cc
  endif
  
+ 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))
  
@@@ -24,23 -26,37 +25,37 @@@ ifeq ("$(origin O)", "command line"
  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
  
  # 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))
+ 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))
  
  # now prefix all objects with object dir
  recv_objs := $(addprefix $(object_dir)/, $(recv_objs))
@@@ -49,7 -65,7 +64,7 @@@ client_objs := $(addprefix $(object_dir
  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))
@@@ -60,59 -76,48 +75,47 @@@ man_pages := $(patsubst %, $(man_dir)/%
  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
  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):
+ $(object_dir) $(man_dir) $(dep_dir) $(m4depdir) $(lls_suite_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))))))))))))))))))))))))))
  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 += $(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
+ STRICT_CFLAGS += -Wformat -Wformat-security -Wmissing-format-attribute
  
 -LDFLAGS += $(clock_gettime_ldflags)
 -
  ifeq ($(uname_s),Linux)
        # these cause warnings on *BSD
        CPPFLAGS += -Wunused-macros
        Q := @
  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)
+       @[ -z "$(Q)" ] || echo 'LLSMAN $<'
+       $(Q) cat $< $(all_commands) > $@
+       $(Q) $(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)/alsa%.o $(dep_dir)/alsa%.d: CPPFLAGS += $(alsa_cppflags)
+ $(object_dir)/mp3_afh.o: CPPFLAGS += $(id3tag_cppflags)
+ $(object_dir)/crypt.o: CPPFLAGS += $(openssl_cppflags)
+ $(object_dir)/gcrypt.o: CPPFLAGS += $(gcrypt_cppflags)
+ $(object_dir)/ao_write.o: CPPFLAGS += $(ao_cppflags)
 -$(object_dir)/aac_afh.o: CPPFLAGS += $(mp4v2_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_afh.o $(dep_dir)/aac_afh.d \
+ $(object_dir)/aacdec_filter.o \
 -$(object_dir)/aac_common.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)
+ $(object_dir)/%.o: %.c | $(object_dir) $(dep_dir) $(lsg_h)
        @[ -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" > $@
+       $(Q) $(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)
@@@ -297,14 -264,29 +260,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 \
@@@ -319,6 -301,13 +297,6 @@@ 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)))
@@@ -326,36 -315,31 +304,36 @@@ $(prefixed_executables)
        @[ -z "$(Q)" ] || echo 'LD $@'
        $(Q) $(CC) $^ -o $@ $(LDFLAGS)
  
 -clean:
 -      @[ -z "$(Q)" ] || echo 'CLEAN'
 +mostlyclean:
 +      @[ -z "$(Q)" ] || echo 'MOSTLYCLEAN'
        $(Q) rm -f para_*
        $(Q) rm -rf $(object_dir)
 -
 -clean2: clean
 -      @[ -z "$(Q)" ] || echo 'CLEAN2'
 +clean: mostlyclean
 +      @[ -z "$(Q)" ] || echo 'CLEAN'
        $(Q) rm -rf $(build_dir)
 -distclean: clean2 test-clean
 +distclean: clean
        @[ -z "$(Q)" ] || echo 'DISTCLEAN'
        $(Q) rm -f Makefile autoscan.log config.status config.log
 -      $(Q) rm -f GPATH GRTAGS GSYMS GTAGS
 -
 +      $(Q) 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
 +      $(Q) rm -f *.tar.bz2 *.tar.xz
 +      $(Q) 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: 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
 +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):
 +$(tarball) dist tarball:
        $(Q) rm -rf $(tarball) $(tarball_pfx)
        $(Q) git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \
                | tar --delete $(tarball_delete) > $(tarball_pfx).tar
        $(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) xz -9 $(tarball_pfx).tar
        $(Q) ls -l $(tarball)
 -      $(Q) ln -sf $(tarball) paraslash-git.tar.bz2
 +      $(Q) ln -sf $(tarball) paraslash-git.tar.xz
        $(Q) rm -rf $(tarball_pfx)
diff --combined NEWS.md
index 39022ad955426c12f68126a875c217b217f90c81,2056f90c06716f40f67fa43d477e63787e780ddb..8a9e49802ef60714723cd5cdb533a1853ce2b56a
+++ b/NEWS.md
@@@ -1,45 -1,6 +1,69 @@@
  NEWS
  ====
  
 +------------------------------------
 +0.6.0 (to be announced) "fuzzy flux"
 +------------------------------------
 +- 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.
 +
 +Downloads:
 +[tarball](./releases/paraslash-git.tar.bz2),
 +
  -------------------------------------
  0.5.7 (2016-12-31) "semantic density"
  -------------------------------------
diff --combined aacdec_filter.c
index e1cf802cb0e4c807e8684957424c7237eee787e4,bbb756a9aa813faf22c952d18e2be739c4590a88..26d5f652c5d8fbd21933290d55cf0296118b9a17
  /** \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
  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. */
@@@ -56,18 -63,11 +55,18 @@@ static int aacdec_execute(struct btr_no
  
  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)
@@@ -85,9 -85,9 +84,9 @@@ static int aacdec_post_select(__a_unuse
        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);
        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 --combined afh.c
index e6c46c3f9d68471ad31529205a46d4590a92b985,8279c577b9afde0354b2a9b8a8f6744c83523905..4955b3cfea12e6e7abf93b672d4501cb1e46ca85
--- 1/afh.c
--- 2/afh.c
+++ b/afh.c
@@@ -7,20 -7,26 +7,26 @@@
  /** \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;
+ 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 -43,38 +43,38 @@@ static int rewrite_tags(const char *nam
        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) {
                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);
@@@ -125,48 -135,44 +135,53 @@@ static void print_info(int audio_format
        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 (OPT_GIVEN(PARSER_FRIENDLY)) {
 -              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 (!conf.parser_friendly_given)
++              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 (!conf.parser_friendly_given)
++              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;
-       ggo_print_help(&h, flags);
-       printf("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
+       char *help;
+       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);
  }
  
@@@ -184,34 -190,37 +199,37 @@@ 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();
+       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();
        afh_init();
-       for (i = 0; i < conf.inputs_num; i++) {
+       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)
+                               if (OPT_GIVEN(CHUNK_TABLE))
 -                                      print_chunk_table(&afhi);
 -                              printf("\n");
 +                                      print_chunk_table(&afhi, audio_format_num,
 +                                              audio_file_data, audio_file_size);
                        }
                        clear_afhi(&afhi);
                }
                        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));
        return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
  }
diff --combined afh_recv.c
index 1eac9a5b38c212ae845ce823574ac3d6718cdba6,b76c405a47c686ad5eec5d0e4c569101ff83738a..9d6effe1e25f6cc40ad0ccf5386ee997bce94862
@@@ -8,15 -8,15 +8,15 @@@
  
  #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,7 -31,6 +31,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)
                        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);
+       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;
        ret = -ERRNO_TO_PARA_ERROR(EINVAL);
        if (afhi->chunks_total == 0)
                goto out_clear_afhi;
-       if (PARA_ABS(conf->begin_chunk_arg) >= afhi->chunks_total)
+       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->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->first_chunk = afh_get_start_chunk(afhi->chunks_total + bc,
 -                      &pard->afhi);
 +                      &pard->afhi, pard->audio_format_num);
-       if (conf->end_chunk_given) {
+       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)
+               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);
@@@ -153,7 -134,6 +137,7 @@@ static void afh_recv_close(struct recei
        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);
  }
  
@@@ -162,13 -142,14 +146,14 @@@ static void afh_recv_pre_select(struct 
        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;
        }
  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);
                        afh_free_header(header, pard->audio_format_num);
                }
        }
-       if (!conf->just_in_time_given) {
+       if (!j_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);
 +              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_INFO_LOG("adding %zu bytes\n", size);
 +                      btr_add_output_dont_free(start, size, btrn);
 +              }
                ret = -E_RECV_EOF;
                goto out;
        }
                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) {
@@@ -250,26 -223,11 +237,11 @@@ 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 = {
+       .init = afh_init,
+       .open = afh_recv_open,
+       .close = afh_recv_close,
+       .pre_select = afh_recv_pre_select,
+       .post_select = afh_recv_post_select,
+       .execute = afh_execute,
+ };
diff --combined afs.c
index 7f27b7dd2653d79a0a7679da3e80d2d9c55e37ac,3a752acaaf9e1631a31704521317967d58886009..75b82c21317a718fbf42ee4c5abaaed61192fbf7
--- 1/afs.c
--- 2/afs.c
+++ b/afs.c
  #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"
@@@ -235,7 -238,7 +238,7 @@@ int send_callback_request(afs_callback 
        *(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 -283,33 +283,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)
        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;
  }
@@@ -590,14 -560,17 +560,17 @@@ static void flush_and_free_pb(struct pa
  
  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;
  
+       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();
  out:
        para_printf(&aca->pbout, "activated %s (%d admissible files)\n",
                current_mop? current_mop : "dummy mood", num_admissible);
+ 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 */
  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,
@@@ -673,8 -655,8 +650,8 @@@ 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(
@@@ -883,7 -865,7 +860,7 @@@ static int execute_server_command(fd_se
                return ret;
        buf[n] = '\0';
        if (strcmp(buf, "new"))
-               return -E_BAD_CMD;
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
        return open_next_audio_file();
  }
  
@@@ -1015,7 -997,7 +992,7 @@@ __noreturn void afs_init(uint32_t cooki
                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);
+       init_admissible_files(OPT_STRING_VAL(AFS_INITIAL_MODE));
        register_command_task(cookie, &s);
        s.default_timeout.tv_sec = 0;
        s.default_timeout.tv_usec = 999 * 1000;
        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;
        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)
        }
        return 1;
  }
+ EXPORT_SERVER_CMD_HANDLER(check);
  
  /**
   * The afs event dispatcher.
diff --combined aft.c
index 22890e569c0fdc73561644d1a346f7792853b38e,3190a9bafa5e4fdd0ef750f3fcf4160057ce7ddf..4a8124481b551b71c584a9483400420f270949d0
--- 1/aft.c
--- 2/aft.c
+++ b/aft.c
@@@ -11,7 -11,9 +11,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"
@@@ -86,18 -88,6 +88,6 @@@ 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 size of the individual output fields of the ls command.
   *
@@@ -128,16 -118,11 +118,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. */
@@@ -335,8 -320,8 +320,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,7 -361,7 +361,7 @@@ static void save_afhi(struct afh_info *
        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;
@@@ -398,7 -383,6 +383,7 @@@ static void load_afhi(const char *buf, 
        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;
        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;
 -
 -      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;
 +      uint32_t n;
 +
 +      if (!afhi->chunk_table)
 +              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->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);
  }
  
  /**
@@@ -634,13 -623,7 +619,13 @@@ static int save_afd(struct audio_file_d
                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;
@@@ -665,22 -648,14 +650,22 @@@ int load_afd(int shmid, struct audio_fi
  {
        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,
@@@ -793,11 -768,11 +778,11 @@@ static void write_image_items(struct pa
  }
  
  static void write_filename_items(struct para_buffer *b, const char *path,
-               unsigned flags)
+               bool basename)
  {
        char *val;
  
-       if (!(flags & LS_FLAG_FULL_PATH)) {
+       if (basename) {
                WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path);
                return;
        }
@@@ -830,11 -805,7 +815,11 @@@ static int print_chunk_table(struct ls_
                (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;
        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];
                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 {
        get_duration_buf(afhi->seconds_total, 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 */
                        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_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_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);
@@@ -996,21 -960,23 +976,23 @@@ out
  
  static int 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;
  
+       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;
        make_inode_status_items(&pb);
        free(parser_friendly_status_items);
        parser_friendly_status_items = pb.buf;
-       return 1;
+       ret = 1;
+ out:
+       lls_free_parse_result(opts.lpr, cmd);
+       return ret;
  }
  
  /**
@@@ -1070,15 -1039,8 +1055,15 @@@ again
        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;
 +      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, &afd->fd);
        if (ret < 0)
                goto out;
        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);
 +      load_chunk_table(&afd->afhi, &chunk_table_obj);
        aced.aft_row = current_aft_row;
        aced.old_afsi = &d->afsi;
        /*
        ret = save_afd(afd);
  out:
        free(afd->afhi.chunk_table);
 -      osl_close_disk_object(&chunk_table_obj);
 +      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);
@@@ -1185,8 -1146,16 +1170,16 @@@ static int ls_path_compare(const void *
        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 *);
                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:
@@@ -1241,15 -1210,16 +1234,16 @@@ static int prepare_ls_row(struct osl_ro
  {
        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;
        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++;
        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);
  
  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,
        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);
                                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)
+ /* TODO: flags -h (sort by hash) */
+ 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 {
+                       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.
@@@ -1546,8 -1462,8 +1486,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. */
   * 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;
  }
@@@ -1644,18 -1561,6 +1585,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;
        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);
        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;
        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)
                        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)
                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 */
                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);
  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)
@@@ -1839,9 -1753,13 +1778,13 @@@ static int add_one_audio_file(const cha
        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;
        }
        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;
                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;
         * 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;
        }
        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);
@@@ -1917,46 -1836,30 +1861,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 */
                }
                free(path);
        }
-       return 1;
+       ret = 1;
+ out:
+       free(pad.slpr);
+       return ret;
  }
+ EXPORT_SERVER_CMD_HANDLER(add);
  
  /**
   * Flags used by the touch command.
@@@ -1987,33 -1894,26 +1919,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);
        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;
  
  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)
  
  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,
        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)
                return ret;
 -      load_afsi(&target_afsi, &target_afsi_obj);
 +      ret = load_afsi(&target_afsi, &target_afsi_obj);
 +      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;
  
  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;
@@@ -2434,9 -2221,8 +2248,8 @@@ static int change_atts(__a_unused struc
  
  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,
                .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 == '+')
                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)
  {
diff --combined audiod.c
index 517958474f39077ebbbf3b6557d4bab6a6ebf82c,2351a8ed82d1a13aa6de9e24f9e25aa23e3aa010..ead29e86b96dae01921ab4167f7231e308e7b76e
+++ b/audiod.c
  #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 "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 -96,9 +96,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
  
@@@ -161,19 -172,10 +172,10 @@@ char *stat_item_values[NUM_STAT_ITEMS] 
   */
  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;
  
  /**
   */
  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 local listening socket. */
 +      int fd;
        /** the associated task structure */
        struct task *task;
  };
@@@ -305,7 -320,6 +307,7 @@@ char *get_time_string(void
                rskip; /* receiver start - sss */
        int slot_num = get_play_time_slot_num();
        struct slot_info *s = slot_num < 0? NULL : &slot[slot_num];
 +      bool writer_active = s && s->wns && s->wns[0].btrn;
        char *msg;
  
        if (audiod_status == AUDIOD_OFF)
        }
        /*
         * Valid status items and playing, set length and tmp to the stream
 -       * start. We use the slot info and fall back to the info from current
 -       * status items if no slot info is available.
 +       * start. We use the writer start time from the slot info and fall back
 +       * to the info from current status items if no writer is active yet.
         */
        tmp = &stat_task->server_stream_start;
 -      if (s && s->wns && s->wns[0].btrn) { /* writer active in this slot */
 +      if (writer_active) {
                btr_get_node_start(s->wns[0].btrn, &wstime);
                if (wstime.tv_sec != 0) { /* writer wrote something */
                        if (s->server_stream_start.tv_sec == 0) {
                tv_diff(tmp, &stat_task->sa_time_diff, &sss);
        else
                tv_add(tmp, &stat_task->sa_time_diff, &sss);
 -      if (!s || !s->wns || !s->wns[0].btrn || wstime.tv_sec == 0) {
 +      if (!writer_active) {
                struct timeval diff;
                tv_diff(now, &sss, &diff);
                seconds = diff.tv_sec + stat_task->offset_seconds;
@@@ -382,58 -396,82 +384,82 @@@ 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);
+       int ret;
+       char *cf, *errctx = NULL;
+       void *map;
+       size_t sz;
+       if (OPT_GIVEN(CONFIG_FILE))
+               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
        else {
                char *home = para_homedir();
-               config_file = make_message("%s/.paraslash/audiod.conf", home);
+               cf = 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);
+       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       if (ret < 0) {
+               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
+                       goto free_cf;
+               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
+                       goto free_cf;
+       } else {
+               int cf_argc;
+               char **cf_argv;
+               struct lls_parse_result *cf_lpr, *merged_lpr;
+               ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
+               para_munmap(map, sz);
+               if (ret < 0)
+                       goto free_cf;
+               cf_argc = ret;
+               ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
+               lls_free_argv(cf_argv);
+               if (ret < 0)
+                       goto free_cf;
+               ret = lls(lls_merge(lpr, cf_lpr, CMD_PTR, &merged_lpr,
+                       &errctx));
+               lls_free_parse_result(cf_lpr, CMD_PTR);
+               if (ret < 0)
+                       goto free_cf;
+               lls_free_parse_result(lpr, CMD_PTR);
+               lpr = merged_lpr;
        }
-       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++) {
+       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       if (OPT_GIVEN(USER_ALLOW)) {
+               uint32_t n = OPT_GIVEN(USER_ALLOW);
+               int i;
+               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(conf.user_allow_arg[i], &val);
+                       ret = para_atoi32(arg, &val);
                        if (ret >= 0) {
                                uid_whitelist[i] = val;
                                continue;
                        }
                        errno = 0; /* see getpwnam(3) */
-                       pw = getpwnam(conf.user_allow_arg[i]);
+                       pw = getpwnam(arg);
                        if (!pw) {
-                               PARA_EMERG_LOG("invalid username: %s\n",
-                                       conf.user_allow_arg[i]);
-                               goto err;
+                               PARA_EMERG_LOG("invalid username: %s\n", arg);
+                               free(uid_whitelist);
+                               goto free_cf;
                        }
                        uid_whitelist[i] = pw->pw_uid;
                }
        }
-       return;
- err:
-       exit(EXIT_FAILURE);
+       ret = 0;
+ free_cf:
+       free(cf);
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               lls_free_parse_result(lpr, CMD_PTR);
+               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
  }
  
  static void setup_signal_handling(void)
@@@ -464,7 -502,7 +490,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);
  
  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);
  }
@@@ -577,17 -612,20 +600,20 @@@ static void open_filters(struct slot_in
        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,
                }, &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));
        }
  }
  
@@@ -612,11 -650,11 +638,11 @@@ static void open_writers(struct slot_in
                * 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));
        }
  }
  
@@@ -626,7 -664,8 +652,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);
        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) {
                btr_remove_node(&rn->btrn);
        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,
@@@ -814,7 -853,7 +841,7 @@@ static int update_item(int itemnum, cha
        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, ':');
        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;
                        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;
                         * 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;
  }
  
@@@ -964,6 -994,7 +982,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;
  
                 * 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);
                /* 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))
+                       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;
@@@ -1006,9 -1036,10 +1024,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;
                }
                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:
@@@ -1047,10 -1078,10 +1066,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",
                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);
  }
  
@@@ -1090,12 -1123,16 +1109,12 @@@ static int signal_post_select(struct sc
  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;
        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;
  
@@@ -1140,7 -1181,7 +1159,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",
@@@ -1233,7 -1274,6 +1252,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);
  }
@@@ -1314,7 -1354,7 +1332,7 @@@ static int status_post_select(struct sc
                        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) {
@@@ -1382,7 -1422,7 +1400,7 @@@ static void init_status_task(struct sta
  {
        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"));
  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.
   *
@@@ -1443,14 -1467,33 +1445,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
   *
@@@ -1465,40 -1508,34 +1486,34 @@@ 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]);
-       }
        init_random_seed_or_die();
+       daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
+       recv_init();
+       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)));
+       }
        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();
        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) {
        sched_shutdown(&sched);
        signal_shutdown(signal_task);
  
+ 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 --combined client_common.c
index ac53b9b547642ceb3e99c4ac47c91068405dec15,c22312c883d06d261eee97bcf9c8432d4f284e7c..a06aaa000b1df567914a00c65ec7cd86b00e6e46
@@@ -13,7 -13,9 +13,9 @@@
  #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 "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
@@@ -46,7 -46,7 +46,7 @@@ void client_close(struct client_task *c
        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]);
@@@ -241,21 -241,28 +241,23 @@@ 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);
@@@ -292,8 -299,8 +294,8 @@@ static int client_post_select(struct sc
        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)
                /* 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)
                        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.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN);
                ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
 -                      SESSION_KEY_LEN, use_aes);
 +                      SESSION_KEY_LEN);
                hash_to_asc(ct->challenge_hash, buf);
                PARA_INFO_LOG("--> %s\n", buf);
                ct->status = CL_RECEIVED_CHALLENGE;
@@@ -460,12 -469,12 +462,12 @@@ int client_connect(struct client_task *
                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;
@@@ -491,13 -500,19 +493,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);
  }
  
  /**
  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));
-       *ct_ptr = ct;
-       ct->scc.fd = -1;
-       ret = -E_CLIENT_SYNTAX;
-       if (client_cmdline_parser(argc, argv, &ct->conf))
-               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;
+       const struct lls_command *cmd = CLIENT_CMD_PTR;
+       void *map;
+       size_t sz;
+       struct lls_parse_result *lpr;
+       int ret, ll;
+       struct client_task *ct;
+       char *cf = NULL, *kf = NULL, *user, *errctx, *home = para_homedir();
+       ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
+       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))
+       ll = CLIENT_OPT_UINT32_VAL(LOGLEVEL, lpr);
+       version_handle_flag("client", CLIENT_OPT_GIVEN(VERSION, lpr));
+       handle_help_flag(lpr);
+       if (CLIENT_OPT_GIVEN(CONFIG_FILE, lpr))
+               cf = para_strdup(CLIENT_OPT_STRING_VAL(CONFIG_FILE, lpr));
+       else
+               cf = make_message("%s/.paraslash/client.conf", home);
+       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       if (ret < 0) {
+               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
+                       goto out;
+               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) &&
+                               CLIENT_OPT_GIVEN(CONFIG_FILE, lpr))
                        goto out;
+       } else {
+               int cf_argc;
+               char **cf_argv;
+               struct lls_parse_result *cf_lpr, *merged_lpr;
+               ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
+               para_munmap(map, sz);
+               if (ret < 0)
+                       goto out;
+               cf_argc = ret;
+               ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
+               lls_free_argv(cf_argv);
+               if (ret < 0)
+                       goto out;
+               ret = lls(lls_merge(lpr, cf_lpr, cmd, &merged_lpr,
+                       &errctx));
+               lls_free_parse_result(cf_lpr, cmd);
+               if (ret < 0)
+                       goto out;
+               lls_free_parse_result(lpr, cmd);
+               lpr = merged_lpr;
        }
-       ct->user = ct->conf.user_given?
-               para_strdup(ct->conf.user_arg) : para_logname();
+       /* success */
+       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);
                }
        }
+       PARA_INFO_LOG("user: %s\n", user);
+       PARA_INFO_LOG("config file: %s\n", cf);
+       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->config_file = cf;
+       ct->user = user;
+       *ct_ptr = ct;
        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;
+               *loglevel = ll;
+       ret = lls_num_inputs(lpr);
  out:
        free(home);
        if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-               client_close(ct);
+               lls_free_parse_result(lpr, cmd);
+               free(cf);
+               free(kf);
                *ct_ptr = NULL;
        }
        return ret;
diff --combined command.c
index 58441b02495cef4465da6c675541b9feb64fce65,fcae6da58382bb82f301ac191396037c14bc8ae2..d0aeea3dc895bf60749a212b1076ef5be8bc18bb
+++ b/command.c
  #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 "crypt.h"
  #include "sideband.h"
  #include "command.h"
- #include "server.cmdline.h"
  #include "string.h"
  #include "afh.h"
  #include "afs.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
@@@ -93,21 -77,6 +77,6 @@@ static char *vss_status_tohuman(unsigne
        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 -92,7 +92,7 @@@ static char *vss_get_status_flags(unsig
        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 */
        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.
   *
@@@ -279,6 -202,81 +202,81 @@@ int send_strerror(struct command_contex
        return send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s\n", para_strerror(err));
  }
  
+ /**
+  * 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 *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 (i = 0; senders[i].name; i++)
+               if (!strcmp(senders[i].name, arg))
+                       break;
+       if (!senders[i].name)
+               return -E_COMMAND_SYNTAX;
+       scd->sender_num = i;
+       arg = lls_input(1, lpr);
+       for (i = 0; subcmds[i]; i++)
+               if (!strcmp(subcmds[i], arg))
+                       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 (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;
+ }
  /**
   * Send a sideband packet through a blocking file descriptor.
   *
@@@ -329,13 -327,13 +327,13 @@@ 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) {
+       if (lls_num_inputs(lpr) == 0) {
                for (i = 0; senders[i].name; i++) {
                        char *tmp;
                        ret = xasprintf(&tmp, "%s%s\n", msg? msg : "",
                }
                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)
+               if (strcmp(lls_input(1, lpr), "status") == 0)
                        msg = senders[scd.sender_num].status();
                else
                        msg = senders[scd.sender_num].help();
        case SENDER_add:
        case SENDER_delete:
                assert(senders[scd.sender_num].resolve_target);
-               ret = senders[scd.sender_num].resolve_target(cc->argv[3], &scd);
+               ret = senders[scd.sender_num].resolve_target(lls_input(2, lpr),
+                       &scd);
                if (ret < 0)
                        return ret;
        }
        }
        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"
                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 \
   *
   * This is used by vss when currently 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;
  }
  #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;
+       bool parser_friendly = SERVER_CMD_OPT_GIVEN(STAT, PARSER_FRIENDLY,
+               lpr) > 0;
+       uint32_t num = SERVER_CMD_UINT32_VAL(STAT, NUM, lpr);
  
        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;
        for (;;) {
                /*
                 * Copy the mmd structure to minimize the time we hold the mmd
  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)
+ /* fixed-length, human readable permission string */
const char *server_cmd_perms_str(unsigned int perms)
  {
-       char *msg = NULL;
+       static char result[5];
  
-       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);
-       }
-       assert(msg);
-       return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
+       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';
+       return result;
  }
  
- /* returns string that must be freed by the caller */
- static struct server_command *get_cmd_ptr(const char *name, char **handler)
+ static int send_list_of_commands(struct command_context *cc)
  {
-       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;
+       int i;
+       const struct lls_command *cmd;
+       char *msg = para_strdup("");
+       for (i = 1; (cmd = lls_cmd(i, server_cmd_suite)); i++) {
+               const char *perms = server_cmd_perms_str(server_command_perms[i]);
+               char *tmp = make_message("%s%s\t%s\t%s\n", msg,
+                       lls_command_name(cmd), perms, lls_purpose(cmd));
+               free(msg);
+               msg = tmp;
+       }
+       return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
  }
  
- /* 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;
+       const char *perms;
+       char *long_help, *buf, *errctx;
        int ret;
+       const struct lls_command *cmd;
  
-       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");
+       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
        }
+       if (lls_num_inputs(lpr) == 0)
+               return send_list_of_commands(cc);
        /* 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);
+       ret = lls(lls_lookup_subcmd(lls_input(0, lpr), server_cmd_suite,
+               &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       cmd = lls_cmd(ret, server_cmd_suite);
+       perms = server_command_perms_txt[ret];
+       long_help = lls_long_help(cmd);
+       assert(long_help);
+       ret = xasprintf(&buf, "%spermissions: %s\n", long_help, perms);
+       free(long_help);
        return send_sb(&cc->scc, buf, ret, SBD_OUTPUT, false);
  }
+ 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;
        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++;
        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(__a_unused struct command_context *cc,
              struct lls_parse_result *lpr)
  {
        long promille;
        int ret, backwards = 0;
        unsigned i;
-       char c;
+       char c, *errctx;
  
-       if (cc->argc != 2)
-               return -E_COMMAND_SYNTAX;
-       if (!(ret = sscanf(cc->argv[1], "%u%c", &i, &c)))
+       ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
+       if (ret < 0) {
+               send_errctx(cc, errctx);
+               return ret;
+       }
+       if (!(ret = sscanf(lls_input(0, lpr), "%u%c", &i, &c)))
                return -E_COMMAND_SYNTAX;
        if (ret > 1 && c == '-')
                backwards = 1; /* jmp backwards */
@@@ -748,17 -709,22 +709,22 @@@ 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(__a_unused struct command_context *cc,
              struct lls_parse_result *lpr)
  {
        long unsigned int i;
        int 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), "%lu", &i) <= 0)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
        mutex_lock(mmd_mutex);
        ret = -E_NO_AUDIO_FILE;
        if (!mmd->afd.afhi.chunks_total)
@@@ -777,26 -743,16 +743,16 @@@ out
        mutex_unlock(mmd_mutex);
        return ret;
  }
+ EXPORT_SERVER_CMD_HANDLER(jmp);
  
- static int com_tasks(struct command_context *cc)
+ static int com_tasks(struct command_context *cc,
+               __a_unused struct lls_parse_result *lpr)
  {
        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)
- {
-       PARA_DEBUG_LOG("checking permissions\n");
-       return (cmd_ptr->perms & perms) < cmd_ptr->perms ? -E_PERM : 0;
+       assert(tl);
+       return send_sb(&cc->scc, tl, strlen(tl), SBD_OUTPUT, false);
  }
+ EXPORT_SERVER_CMD_HANDLER(tasks);
  
  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,
                        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;
@@@ -856,37 -812,50 +812,50 @@@ out
  static int run_command(struct command_context *cc, struct iovec *iov,
                const char *peername)
  {
-       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,
+       argv[argc] = NULL;
+       PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", lls_command_name(lcmd),
                cc->u->name, peername);
-       ret = cmd->handler(cc);
-       free_argv(cc->argv);
+       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;
@@@ -994,9 -963,10 +963,9 @@@ __noreturn void handle_connect(int fd, 
        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.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN);
        cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
 -              SESSION_KEY_LEN, cf.aes_ctr128_requested);
 +              SESSION_KEY_LEN);
        ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false);
        if (ret < 0)
                goto net_err;
diff --combined configure.ac
index 929d44f35fd482a83d3713d5ab24ffe9b0edd8db,b2ea0a72b2eda4548189d189c50ed24320002da9..a589613b5ad5f000edb65f77e6f73101f1d34ae2
@@@ -51,22 -51,35 +51,18 @@@ AC_DEFUN([LIB_SUBST_FLAGS], 
  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_PATH_PROG([lopsubgen], [lopsubgen])
+ test -z "$lopsubgen" && AC_MSG_ERROR(
+       [lopsubgen 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
 -
  ########################################################################### osl
  STASH_FLAGS
  LIB_ARG_WITH([osl], [-losl])
@@@ -75,6 -88,22 +71,22 @@@ 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
+ STASH_FLAGS
+ LIB_ARG_WITH([lopsub], [-llopsub])
+ HAVE_LOPSUB=yes
+ AC_CHECK_HEADER(lopsub.h, [], [HAVE_LOPSUB=no])
+ AC_CHECK_LIB([lopsub], [lls_merge], [], [HAVE_LOPSUB=yes])
+ 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
@@@ -165,15 -194,6 +177,6 @@@ AC_MSG_RESULT($have_ucred
  if test ${have_ucred} = yes; then
        AC_DEFINE(HAVE_UCRED, 1, define to 1 you have struct ucred)
  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], [])
@@@ -196,6 -216,24 +199,6 @@@ 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])
@@@ -262,12 -300,10 +265,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
@@@ -344,11 -380,18 +347,10 @@@ AC_CHECK_HEADER(samplerate.h, [], HAVE_
  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
        build_server="yes"
        executables="$executables server"
-       server_cmdline_objs="server"
        server_errlist_objs="
                server
                afh_common
                wma_common
                sideband
                version
-               ggo
        "
        if test "$CRYPTOLIB" = openssl; then
                server_errlist_objs="$server_errlist_objs crypt"
        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"
@@@ -411,7 -453,6 +412,6 @@@ f
  if test -n "$CRYPTOLIB"; then
        build_client="yes"
        executables="$executables client"
-       client_cmdline_objs="client"
        client_errlist_objs="
                client
                net
                crypt_common
                base64
                version
-               ggo
        "
        if test "$CRYPTOLIB" = openssl; then
                client_errlist_objs="$client_errlist_objs crypt"
        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
@@@ -447,18 -487,6 +446,6 @@@ if test -n "$CRYPTOLIB"; the
        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
                audiod_command
                fecdec_filter
                client_common
-               ggo
                udp_recv
                color
                fec
        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"
 -      fi
        NEED_VORBIS_OBJECTS && {
                audiod_errlist_objs="$audiod_errlist_objs oggdec_filter"
                audiod_audio_formats="$audiod_audio_formats ogg"
                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)"
  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 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
                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
        sched
        fd
        amp_filter
-       ggo
        fecdec_filter
        fec
        version
        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"
 +      filter_errlist_objs="$filter_errlist_objs aacdec_filter"
-       filters="$filters aacdec"
  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
        fd
        sched
        stdout
-       ggo
        udp_recv
        buffer_tree
        afh_recv
@@@ -743,15 -688,13 +644,13 @@@ NEED_SPEEX_OBJECTS && recv_errlist_objs
  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
        wma_afh
        wma_common
        version
-       ggo
  "
  NEED_OGG_OBJECTS && afh_errlist_objs="$afh_errlist_objs ogg_afh_common"
  NEED_VORBIS_OBJECTS && {
@@@ -781,12 -723,12 +679,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
@@@ -794,7 -736,6 +692,6 @@@ play_errlist_objs=
        play
        fd
        sched
-       ggo
        buffer_tree
        time
        string
        version
        sync_filter
  "
- 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"
 -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"
@@@ -852,39 -784,36 +737,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
        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"
 -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
        net
        fd
        version
-       ggo
  "
  if test $HAVE_READLINE = yes; then
        audioc_errlist_objs="$audioc_errlist_objs
                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
  
@@@ -961,7 -868,7 +812,7 @@@ attributes_txt decoder_flags audiod_sta
  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"
 +comment max_chunk_size"
  
  result=
  for i in $status_items; do
@@@ -991,14 -898,12 +842,11 @@@ unix socket credentials: $have_ucre
  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 --combined error.h
index 32e525cd36e220e28a61359a008889e5b1e2c08b,56dbea2b888936321f2666bb0e2ee6e7ec4ac882..58fcd3757c64acda473456de39196a7a6766fde3
+++ b/error.h
@@@ -8,9 -8,11 +8,9 @@@
  
  /** Codes and messages. */
  #define PARA_ERRORS \
 -      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(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"), \
        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(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(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(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_PRINT, "could not convert multi-precision integer"), \
        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(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(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"), \
@@@ -282,19 -278,28 +273,28 @@@ extern const char * const para_errlist[
   */
  #define OSL_ERROR_BIT 29
  
+ #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).
   *
@@@ -309,6 -314,10 +309,10 @@@ _static_inline_ const char *para_strerr
                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];
@@@ -331,3 -340,16 +335,16 @@@ _static_inline_ int osl(int ret
                return ret;
        return -OSL_ERRNO_TO_PARA_ERROR(-ret);
  }
+ /**
+  * Wrapper for lopsub library calls.
+  *
+  * \param ret See osl().
+  * \return See osl().
+  */
+ _static_inline_ int lls(int ret)
+ {
+       if (ret >= 0)
+               return ret;
+       return -LLS_ERRNO_TO_PARA_ERROR(-ret);
+ }
diff --combined server.h
index 7f276ab92edb4d06d312fc1c1c12f0003b7ae9ac,68836e93f175b9733de839c695ccf3f36e30541d..0bfca305fc5b6465aec22db6b5b0c155a682a141
+++ b/server.h
@@@ -48,6 -48,8 +48,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. */
        struct audio_file_data afd;
  };
  
- /** Command line options for para_server. */
- extern struct server_args_info conf;
+ 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);
+ void parse_config_or_die(bool reload);
  char *server_get_tasks(void);
diff --combined vss.c
index 32e36a997fadadca81df24e12a776b974423420e,d1cf538360dfa8c09cd42bfae1368a2115f85573..3632cf54557350f35dc19ec8a2ce07ce9a6d5c0f
--- 1/vss.c
--- 2/vss.c
+++ b/vss.c
@@@ -19,7 -19,9 +19,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"
@@@ -29,7 -31,6 +31,6 @@@
  #include "afs.h"
  #include "server.h"
  #include "net.h"
- #include "server.cmdline.h"
  #include "list.h"
  #include "send.h"
  #include "sched.h"
@@@ -88,8 -89,6 +89,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. */
@@@ -98,8 -97,6 +99,8 @@@
        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;
  };
  
  /**
@@@ -351,8 -348,6 +352,8 @@@ static int initialize_fec_client(struc
  static void 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
         * 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;
        }
 -      afh_get_chunk(chunk_num, &mmd->afd.afhi, vsst->map, (const char **)buf,
 -              sz);
 +      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) {
 +              PARA_WARNING_LOG("could not get chunk %d: %s\n",
 +                      chunk_num, para_strerror(-ret));
 +              *buf = NULL;
 +              *sz = 0;
 +      }
  }
  
  static void compute_group_size(struct vss_task *vsst, struct fec_group *g,
@@@ -860,7 -847,7 +861,7 @@@ static void vss_eof(struct vss_task *vs
        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;
        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++;
  }
  
@@@ -989,11 -974,11 +990,11 @@@ static void recv_afs_result(struct vss_
                ret = -ERRNO_TO_PARA_ERROR(errno);
                goto err;
        }
 -      mmd->size = statbuf.st_size;
 -      ret = para_mmap(mmd->size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
 +      ret = para_mmap(statbuf.st_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
                passed_fd, 0, &vsst->map);
        if (ret < 0)
                goto err;
 +      vsst->mapsize = statbuf.st_size;
        close(passed_fd);
        mmd->chunks_sent = 0;
        mmd->current_chunk = 0;
        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);
@@@ -1092,7 -1077,7 +1093,7 @@@ static void vss_send(struct vss_task *v
                 */
                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++) {
 +                      for (i = 0; i < 5 && buf < vsst->map + vsst->mapsize; i++) {
                                __a_unused volatile char x = *buf;
                                buf += 4096;
                        }
@@@ -1129,7 -1114,7 +1130,7 @@@ static int vss_post_select(struct sche
                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();
        }
@@@ -1182,10 -1167,8 +1183,8 @@@ void init_vss_task(int afs_socket, stru
        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);
        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;
diff --combined web/manual.md
index dff8a22a659c7c61f33ce6fef8534dd89f133526,1fde2953a5b4494e42c0ccc5a856228c2f85805e..5216c6b5cf03153b797611776045e6ed06bd6110
@@@ -152,8 -152,9 +152,8 @@@ an array of offsets within the audio fi
  ### para_write ###
  
  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 ###
  
@@@ -166,7 -167,7 +166,7 @@@ window. Appearance can be customized vi
  key-bindings for the most common server commands and new key-bindings
  can be added easily.
  
- ### para_fade ###
+ ### para_mixer ###
  
  An alarm clock and volume-fader for OSS and ALSA.
  
@@@ -186,7 -187,7 +186,7 @@@ Requirement
  
        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 \
                libasound2-dev libao-dev libreadline-dev libncurses-dev \
@@@ -202,6 -203,13 +202,13 @@@ code repository, execut
  
                git clone git://git.tuebingen.mpg.de/osl
  
+ - [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/lopsub
  - [gcc](ftp://ftp.gnu.org/pub/gnu/gcc) or
  [clang](http://clang.llvm.org). All gcc versions >= 4.2 are currently
  supported. Clang version 1.1 or newer should work as well.
@@@ -213,13 -221,6 +220,6 @@@ disto. On BSD systems the gnu make exec
  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.
  
@@@ -248,11 -249,8 +248,11 @@@ recognized. The mp3 tagger also needs t
  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](http://www.audiocoding.com/). 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
  files, libspeex (`libspeex-dev`) is required.
@@@ -450,9 -448,9 +450,9 @@@ User managemen
  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).
@@@ -460,33 -458,33 +460,33 @@@ These sections are all about communicat
  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
  ----------------------------
@@@ -526,8 -524,8 +526,8 @@@ point on the communication is encrypte
  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
@@@ -1702,6 -1700,10 +1702,6 @@@ emulation for backwards compatibility. 
  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
  it through a sound device. It is supported on all platforms and is
diff --combined wmadec_filter.c
index 692ea0f3dde1d25996a874aff00e1a6a1f7b043d,18997aec47ea67ba0740e7d7dd844c48f9e5db22..525ed3150cda5affb9b479029b7e9aa5f461a0aa
  #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"
@@@ -1260,16 -1258,10 +1259,10 @@@ static void wmadec_open(struct filter_n
        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,
+ };