From: Andre Noll Date: Thu, 13 Jul 2017 15:21:13 +0000 (+0200) Subject: Merge branch 'maint' X-Git-Tag: v0.6.1~69 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=8d106ca317a2c42f35a86ba244f843688f6939e6;hp=c62b1c7be957398d6641b00f2282fb7235091a3c Merge branch 'maint' A single important fix for the error subsystem. The merge conflicted because master removed the E_AAC_AFH_INIT error code while maint still has it. The conflict was trivial to resolve. * refs/heads/maint: Let error codes start out at index 1. --- diff --git a/.gitignore b/.gitignore index 10a7a8a9..10d2572d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ config.status Makefile TODO paraslash-*.tar.bz2 +paraslash-*.tar.xz web/dia/overview.pdf *.swp *.rej diff --git a/Doxyfile b/Doxyfile index c607b8ea..70c71261 100644 --- a/Doxyfile +++ b/Doxyfile @@ -795,10 +795,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = *.cmdline.* \ - gcc-compat.h \ - *.command_list.h \ - *.completion.h +EXCLUDE_PATTERNS = gcc-compat.h # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -896,7 +893,7 @@ USE_MDFILE_AS_MAINPAGE = # also VERBATIM_HEADERS is set to NO. # The default value is: NO. -SOURCE_BROWSER = YES +SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. @@ -961,7 +958,7 @@ SOURCE_TOOLTIPS = YES # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. -USE_HTAGS = YES +USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is @@ -969,7 +966,7 @@ USE_HTAGS = YES # See also: Section \class. # The default value is: YES. -VERBATIM_HEADERS = YES +VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index diff --git a/INSTALL b/INSTALL index 85b4fab1..4a86e967 100644 --- a/INSTALL +++ b/INSTALL @@ -1,5 +1,11 @@ Any knowledge of how to work with mouse and icons is not required. +Installing lopsub +~~~~~~~~~~~~~~~~~ + git clone git://git.tuebingen.mpg.de/lopsub + cd lopsub && make && sudo make install + (see http://people.tuebingen.mpg.de/maan/lopsub/) + Installing osl ~~~~~~~~~~~~~~ git clone git://git.tuebingen.mpg.de/osl @@ -16,10 +22,8 @@ Installing paraslash from git Example for cross-compiling ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - export CROSS_COMPILE='armv6j-hardfloat-linux-gnueabi-' + export CC='armv6j-hardfloat-linux-gnueabi-gcc' export PATH="/usr/cross/arm/bin:$PATH" - export CC=${CROSS_COMPILE}gcc - export LDFLAGS=' -L/usr/sysroot/arm/lib -L/usr/sysroot/arm/usr/lib @@ -29,7 +33,7 @@ Example for cross-compiling autoconf autoheader ./configure --host=arm-linux-gnueabi --prefix /usr/sysroot/arm/usr/local - make CROSS_COMPILE=$CROSS_COMPILE + make For details see the user manual: diff --git a/Makefile.in b/Makefile.in index ec55c8e3..b3e3be5c 100644 --- a/Makefile.in +++ b/Makefile.in @@ -8,17 +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@ @@ -26,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@ @@ -39,6 +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@ @@ -50,9 +43,7 @@ samplerate_cppflags := @samplerate_cppflags@ readline_cppflags := @readline_cppflags@ alsa_cppflags := @alsa_cppflags@ oss_cppflags := @oss_cppflags@ -mp4v2_cppflags := @mp4v2_cppflags@ -clock_gettime_ldflags := @clock_gettime_ldflags@ id3tag_ldflags := @id3tag_ldflags@ ogg_ldflags := @ogg_ldflags@ vorbis_ldflags := @vorbis_ldflags@ @@ -61,6 +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@ @@ -69,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 --git a/Makefile.real b/Makefile.real index 8ededf6a..3631a5c9 100644 --- a/Makefile.real +++ b/Makefile.real @@ -7,9 +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)) @@ -25,23 +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)) @@ -50,7 +64,7 @@ client_objs := $(addprefix $(object_dir)/, $(client_objs)) gui_objs := $(addprefix $(object_dir)/, $(gui_objs)) audiod_objs := $(addprefix $(object_dir)/, $(audiod_objs)) audioc_objs := $(addprefix $(object_dir)/, $(audioc_objs)) -fade_objs := $(addprefix $(object_dir)/, $(fade_objs)) +mixer_objs := $(addprefix $(object_dir)/, $(mixer_objs)) server_objs := $(addprefix $(object_dir)/, $(server_objs)) write_objs := $(addprefix $(object_dir)/, $(write_objs)) afh_objs := $(addprefix $(object_dir)/, $(afh_objs)) @@ -61,59 +75,46 @@ man_pages := $(patsubst %, $(man_dir)/%.1, $(prefixed_executables)) autocrap := config.h.in configure tarball_pfx := $(PACKAGE_TARNAME)-$(GIT_VERSION) tarball_delete := $(addprefix $(tarball_pfx)/, web .gitignore) -tarball := $(tarball_pfx).tar.bz2 +tarball := $(tarball_pfx).tar.xz -.PHONY: all clean clean2 distclean maintainer-clean install man tarball all: $(prefixed_executables) $(man_pages) +.PHONY: all mostlyclean clean distclean maintainer-clean install \ + install-strip man dist tarball + man: $(man_pages) -tarball: $(tarball) -include $(m4_ggo_dir)/makefile +include $(lls_m4_dir)/makefile include $(test_dir)/makefile.test 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 - -LDFLAGS += $(clock_gettime_ldflags) +STRICT_CFLAGS += -Wformat -Wformat-security -Wmissing-format-attribute ifeq ($(uname_s),Linux) # these cause warnings on *BSD @@ -140,149 +141,107 @@ else 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)/aac_afh.o $(dep_dir)/aac_afh.d: CPPFLAGS += $(mp4v2_cppflags) -$(object_dir)/alsa%.o $(dep_dir)/alsa%.d: CPPFLAGS += $(alsa_cppflags) +$(object_dir)/mp3_afh.o: CPPFLAGS += $(id3tag_cppflags) +$(object_dir)/crypt.o: CPPFLAGS += $(openssl_cppflags) +$(object_dir)/gcrypt.o: CPPFLAGS += $(gcrypt_cppflags) +$(object_dir)/ao_write.o: CPPFLAGS += $(ao_cppflags) +$(object_dir)/alsa%.o: CPPFLAGS += $(alsa_cppflags) -$(object_dir)/interactive.o $(dep_dir)/interactive.d \ +$(object_dir)/interactive.o \ : CPPFLAGS += $(readline_cppflags) -$(object_dir)/resample_filter.o $(dep_dir)/resample_filter.d \ +$(object_dir)/resample_filter.o \ : CPPFLAGS += $(samplerate_cppflags) -$(object_dir)/oss_write.o $(dep_dir)/oss_write.d \ +$(object_dir)/oss_write.o \ : CPPFLAGS += $(oss_cppflags) -$(object_dir)/ao_write.o $(dep_dir)/ao_write.d \ +$(object_dir)/ao_write.o \ : CPPFLAGS += $(ao_cppflags) $(pthread_cppflags) -$(object_dir)/mp3dec_filter.o $(dep_dir)/mp3dec_filter.d \ +$(object_dir)/mp3dec_filter.o \ : CPPFLAGS += $(mad_cppflags) -$(object_dir)/aacdec_filter.o $(dep_dir)/aacdec_filter.d \ -$(object_dir)/aac_common.o $(dep_dir)/aac_common.d \ -$(object_dir)/aac_afh.o $(dep_dir)/aac_afh.d \ +$(object_dir)/aacdec_filter.o \ +$(object_dir)/aac_afh.o \ : CPPFLAGS += $(faad_cppflags) -$(object_dir)/ogg_afh.o $(dep_dir)/ogg_afh.d \ -$(object_dir)/oggdec_filter.o $(dep_dir)/oggdec_filter.d \ +$(object_dir)/ogg_afh.o \ +$(object_dir)/oggdec_filter.o \ : CPPFLAGS += $(vorbis_cppflags) -$(object_dir)/spx_common.o $(dep_dir)/spx_common.d \ -$(object_dir)/spxdec_filter.o $(dep_dir)/spxdec_filter.d \ -$(object_dir)/spx_afh.o $(dep_dir)/spx_afh.d \ -$(object_dir)/oggdec_filter.o $(dep_dir)/oggdec_filter.d \ -$(object_dir)/ogg_afh.o $(dep_dir)/ogg_afh.d \ -$(object_dir)/ogg_afh_common.o $(dep_dir)/ogg_afh_common.d \ -$(object_dir)/opus%.o $(dep_dir)/opus%.d \ +$(object_dir)/spx_common.o \ +$(object_dir)/spxdec_filter.o \ +$(object_dir)/spx_afh.o \ +$(object_dir)/oggdec_filter.o \ +$(object_dir)/ogg_afh.o \ +$(object_dir)/ogg_afh_common.o \ +$(object_dir)/opus%.o \ : CPPFLAGS += $(ogg_cppflags) -$(object_dir)/afs.o $(dep_dir)/afs.d \ -$(object_dir)/aft.o $(dep_dir)/aft.d \ -$(object_dir)/attribute.o $(dep_dir)/attribute.d \ -$(object_dir)/blob.o $(dep_dir)/blob.d \ -$(object_dir)/mood.o $(dep_dir)/mood.d \ -$(object_dir)/playlist.o $(dep_dir)/playlist.d \ -$(object_dir)/score.o $(dep_dir)/score.d \ -$(object_dir)/server.o $(dep_dir)/server.d \ -$(object_dir)/vss.o $(dep_dir)/vss.d \ -$(object_dir)/command.o $(dep_dir)/command.d \ -$(object_dir)/http_send.o $(dep_dir)/http_send.d \ -$(object_dir)/dccp_send.o $(dep_dir)/dccp_send.d \ -$(object_dir)/udp_send.o $(dep_dir)/udp_send.d \ -$(object_dir)/send_common.o $(dep_dir)/send_common.d \ -$(object_dir)/mm.o $(dep_dir)/mm.d \ +$(object_dir)/afs.o \ +$(object_dir)/aft.o \ +$(object_dir)/attribute.o \ +$(object_dir)/blob.o \ +$(object_dir)/mood.o \ +$(object_dir)/playlist.o \ +$(object_dir)/score.o \ +$(object_dir)/server.o \ +$(object_dir)/vss.o \ +$(object_dir)/command.o \ +$(object_dir)/http_send.o \ +$(object_dir)/dccp_send.o \ +$(object_dir)/udp_send.o \ +$(object_dir)/send_common.o \ +$(object_dir)/mm.o \ : CPPFLAGS += $(osl_cppflags) -$(object_dir)/%.cmdline.o: CFLAGS += -Wno-unused-function $(object_dir)/compress_filter.o: CFLAGS += -O3 -$(object_dir)/%.o: %.c | $(object_dir) - @[ -z "$(Q)" ] || echo 'CC $<' - $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(STRICT_CFLAGS) $< - -$(object_dir)/%.cmdline.o: $(cmdline_dir)/%.cmdline.c $(cmdline_dir)/%.cmdline.h | $(object_dir) +$(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) @@ -301,14 +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 \ @@ -323,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))) @@ -337,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 @@ -369,7 +341,7 @@ $(tarball): $(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 --git a/NEWS.md b/NEWS.md index 6cb098c5..60b94238 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,81 @@ NEWS ==== ------------------------------------------- -0.5.7 (to be announced) "semantic density" ------------------------------------------- +------------------------------- +0.6.0 (2017-04-28) "fuzzy flux" +------------------------------- + +The highlights of this release are the conversion of all option parsers +to the lopsub library and the improved AAC support. But there are +many other user-visible changes all over the place. Also a lot of +old cruft has been removed, leading to incompatible changes. Hence +the new major version number. + +- Support for Mac OS X has been removed. +- On Linux systems, glibc-2.17 or newer is required to build the + source tree. +- Support for RSA public keys in ASN format (as generated by openssl + genrsa) has been removed. These keys have been deprecated since + 2011, so users should have long switched to keys generated with + ssh-keygen(1). +- If libgcrypt is used as the crypto library, we now require version + 1.5.0 (released in 2011) or later. +- The insecure RC4 stream cipher has been removed. It was superseded + by aes_ctr128 three years ago but the RC4 code had been kept for + backwards compatibility. +- On Linux, abstract unix domain sockets are used unconditionally. +- The "install" target no longer strips executables, the new + install-strip target can be used to get the old behaviour. +- The clean targets have been renamed: clean2 is gone, and the new + mostlyclean removes only the executables and object files. +- New target: check (identical to test). +- The DESTDIR make variable is honored to prepend a path to the + installation directory. This feature is orthogonal to the --prefix + option to configure. +- Minor WMA cleanups. +- The aac audio format handler has been rewritten to use the mp4ff library. + See the manual for how to install the library on your system. +- New status item: max_chunk_size. The value is stored in a previously + unused field of the afhi object of the aft table. Although backwards + compatible, users are encouraged to re-add m4a files to populate + the new field. +- No more chunk tables for aac. Chunk boundaries are determined + dynamically at stream time. +- Release and master branch tarballs are now compressed with xz rather + than bzip2. +- The lopsub package is required to install the paraslash package. + Gengetopt is no longer needed. +- make dep is gone. Dependencies have been created automatically for + a long time and it was never necessary to run make dep manually. +- para_gui lost its --timeout option. +- Most manual pages have been extended to include an overall + description of the command. +- The --stream-delay option of para_audiod has been removed. It had + been a no-op for many years. +- The deprecated --path option of the server ls command has been + removed. The command now prints full paths by default, making + --full-path a no-op. Hence --full-path has been depreacted and is + scheduled for removal in v0.6.1. +- It is now possible to use 'CFLAGS' to override the default compiler + options. +- para_fade has been renamed to para_mixer. The four modes of operation + (set, fade, snooze, sleep) are implemented as subcommands of the + new program. +- The sleep subcommand of para_mixer (former sleep mode of para_fade) + lost its --wake-hour and --wake-min options in favor of the new + --wake-time option which also accepts relative wakeup times like + "+9:30". +- The new --fade-exponent option of para_mixer allows for non-linear + channel fading. +- The new logo. + +Downloads: +[tarball](./releases/paraslash-0.6.0.tar.xz), +[signature](./releases/paraslash-0.6.0.tar.xz.asc) + +------------------------------------- +0.5.7 (2016-12-31) "semantic density" +------------------------------------- Mostly a bug fix release, and a bunch of internal improvements. The only user-visible changes are the sanity checks for the touch @@ -24,7 +96,9 @@ command and the new options to the ls command. - New section on contributing for the user manual. - Major simplification of the error subsystem. -Download: [tarball](./releases/paraslash-git.tar.bz2) +Downloads: +[tarball](./releases/paraslash-0.5.7.tar.bz2), +[signature](./releases/paraslash-0.5.7.tar.bz2.asc) ------------------------------------------- 0.4.14 (2016-12-31) "branching oscillation" diff --git a/aac.h b/aac.h deleted file mode 100644 index eeed2528..00000000 --- a/aac.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (C) 2006 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file aac.h Exported symbols from aac_common.c. */ - -#include - -NeAACDecHandle aac_open(void); -int aac_find_esds(char *buf, size_t buflen, size_t *skip, - unsigned long *decoder_length); -ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip); diff --git a/aac_afh.c b/aac_afh.c index 1c7fd706..8550a8ac 100644 --- a/aac_afh.c +++ b/aac_afh.c @@ -11,308 +11,328 @@ /** \file aac_afh.c para_server's aac audio format handler. */ #include -#include +#include #include "para.h" + +/* To get the mp4ff_tag_t and mp4ff_metadata_t typedefs. */ +#define USE_TAGGING +#include + #include "error.h" #include "portable_io.h" #include "afh.h" #include "string.h" -#include "aac.h" #include "fd.h" -static int aac_find_stsz(char *buf, size_t buflen, off_t *skip) + +struct aac_afh_context { + const void *map; + size_t mapsize; + size_t fpos; + int32_t track; + mp4ff_t *mp4ff; + mp4AudioSpecificConfig masc; + mp4ff_callback_t cb; +}; + +static uint32_t aac_afh_read_cb(void *user_data, void *dest, uint32_t want) { - int i; - - for (i = 0; i + 16 < buflen; i++) { - char *p = buf + i; - unsigned sample_count, sample_size; - - if (p[0] != 's' || p[1] != 't' || p[2] != 's' || p[3] != 'z') - continue; - PARA_DEBUG_LOG("found stsz@%d\n", i); - i += 8; - sample_size = read_u32_be(buf + i); - PARA_DEBUG_LOG("sample size: %u\n", sample_size); - i += 4; - sample_count = read_u32_be(buf + i); - i += 4; - PARA_DEBUG_LOG("sample count: %u\n", sample_count); - *skip = i; - return sample_count; + struct aac_afh_context *c = user_data; + uint32_t have, rv; + + if (want == 0 || c->fpos >= c->mapsize) { + PARA_INFO_LOG("failed attempt to read %u bytes @%zu\n", want, + c->fpos); + errno = EAGAIN; + return -1; } - return -E_STSZ; + have = c->mapsize - c->fpos; + rv = PARA_MIN(have, want); + PARA_DEBUG_LOG("reading %u bytes @%zu\n", rv, c->fpos); + memcpy(dest, c->map + c->fpos, rv); + c->fpos += rv; + return rv; } -static int atom_cmp(const char *buf1, const char *buf2) +static uint32_t aac_afh_seek_cb(void *user_data, uint64_t pos) { - return memcmp(buf1, buf2, 4)? 1 : 0; + struct aac_afh_context *c = user_data; + c->fpos = pos; + return 0; } -static int read_atom_header(char *buf, uint64_t *subsize, char type[5]) +static int32_t aac_afh_get_track(mp4ff_t *mp4ff, mp4AudioSpecificConfig *masc) { - uint64_t size = read_u32_be(buf); - - memcpy(type, buf + 4, 4); - type[4] = '\0'; - - PARA_DEBUG_LOG("size: %llu, type: %s\n", (long long unsigned)size, type); - if (size != 1) { - *subsize = size; - return 8; + int32_t i, rc, num_tracks = mp4ff_total_tracks(mp4ff); + + assert(num_tracks >= 0); + for (i = 0; i < num_tracks; i++) { + unsigned char *buf = NULL; + unsigned buf_size = 0; + + mp4ff_get_decoder_config(mp4ff, i, &buf, &buf_size); + if (buf) { + rc = NeAACDecAudioSpecificConfig(buf, buf_size, masc); + free(buf); + if (rc < 0) + continue; + return i; + } } - buf += 4; - size = 0; - size = read_u64_be(buf); - *subsize = size; - return 16; + return -1; /* no audio track */ } -static char *get_tag(char *p, int size) +static int aac_afh_open(const void *map, size_t mapsize, void **afh_context) { - char *buf; - - assert(size > 0); - buf = para_malloc(size + 1); - - memcpy(buf, p, size); - buf[size] = '\0'; - PARA_DEBUG_LOG("size: %d: %s\n", size, buf); - return buf; + int ret; + struct aac_afh_context *c = para_malloc(sizeof(*c)); + + c->map = map; + c->mapsize = mapsize; + c->fpos = 0; + c->cb.read = aac_afh_read_cb; + c->cb.seek = aac_afh_seek_cb; + c->cb.user_data = c; + + ret = -E_MP4FF_OPEN; + c->mp4ff = mp4ff_open_read(&c->cb); + if (!c->mp4ff) + goto free_ctx; + c->track = aac_afh_get_track(c->mp4ff, &c->masc); + ret = -E_MP4FF_TRACK; + if (c->track < 0) + goto close_mp4ff; + *afh_context = c; + return 0; +close_mp4ff: + mp4ff_close(c->mp4ff); +free_ctx: + free(c); + *afh_context = NULL; + return ret; } -static void read_tags(char *buf, size_t buflen, struct afh_info *afhi) +static void aac_afh_close(void *afh_context) { - char *p = buf; + struct aac_afh_context *c = afh_context; + mp4ff_close(c->mp4ff); + free(c); +} - while (p + 32 < buf + buflen) { - char *q, type1[5], type2[5]; - uint64_t size1, size2; - int ret, ret2; +/** + * Libmp4ff function to reposition the file to the given sample. + * + * \param f The opaque handle returned by mp4ff_open_read(). + * \param track The number of the (audio) track. + * \param sample Destination. + * + * We need this function to obtain the offset of the sample within the audio + * file. Unfortunately, it is not exposed in the mp4ff header. + * + * \return This function always returns 0. + */ +int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample); - ret = read_atom_header(p, &size1, type1); - ret2 = read_atom_header(p + ret, &size2, type2); +static int aac_afh_get_chunk(long unsigned chunk_num, void *afh_context, + const char **buf, size_t *len) +{ + struct aac_afh_context *c = afh_context; + int32_t ss; + size_t offset; + + assert(chunk_num <= INT_MAX); + /* this function always returns zero */ + mp4ff_set_sample_position(c->mp4ff, c->track, chunk_num); + offset = c->fpos; + ss = mp4ff_read_sample_getsize(c->mp4ff, c->track, chunk_num); + if (ss <= 0) + return -E_MP4FF_BAD_SAMPLE; + assert(ss + offset <= c->mapsize); + *buf = c->map + offset; + *len = ss; + return 1; +} - if (size2 <= 16 || atom_cmp(type2, "data")) { - p += size1; - continue; - } - size2 -= 16; - q = p + ret + ret2 + 8; - if (q + size2 > buf + buflen) - break; - if (!atom_cmp(type1, "\xa9" "ART")) - afhi->tags.artist = get_tag(q, size2); - else if (!atom_cmp(type1, "\xa9" "alb")) - afhi->tags.album = get_tag(q, size2); - else if (!atom_cmp(type1, "\xa9" "nam")) - afhi->tags.title = get_tag(q, size2); - else if (!atom_cmp(type1, "\xa9" "cmt")) - afhi->tags.comment = get_tag(q, size2); - else if (!atom_cmp(type1, "\xa9" "day")) - afhi->tags.year = get_tag(q, size2); - p += size1; - } +static void _aac_afh_get_taginfo(const mp4ff_t *mp4ff, struct taginfo *tags) +{ + mp4ff_meta_get_artist(mp4ff, &tags->artist); + mp4ff_meta_get_title(mp4ff, &tags->title); + mp4ff_meta_get_date(mp4ff, &tags->year); + mp4ff_meta_get_album(mp4ff, &tags->album); + mp4ff_meta_get_comment(mp4ff, &tags->comment); } -static void read_meta(char *buf, size_t buflen, struct afh_info *afhi) +/* + * Init m4a file and write some tech data to given pointers. + */ +static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd, + struct afh_info *afhi) { - char *p = buf; + int ret; + int32_t rv; + struct aac_afh_context *c; + int64_t tmp; + const char *buf; + size_t sz; + uint32_t n; + + ret = aac_afh_open(map, numbytes, (void **)&c); + if (ret < 0) + return ret; - while (p + 4 < buf + buflen) { + ret = -E_MP4FF_BAD_SAMPLERATE; + rv = mp4ff_get_sample_rate(c->mp4ff, c->track); + if (rv <= 0) + goto close; + afhi->frequency = rv; - if (p[0] != 'i' || p[1] != 'l' || p[2] != 's' || p[3] != 't') { - p++; - continue; - } - p += 4; - return read_tags(p, buflen - (p - buf), afhi); + ret = -E_MP4FF_BAD_CHANNEL_COUNT; + rv = mp4ff_get_channel_count(c->mp4ff, c->track); + if (rv <= 0) + goto close; + afhi->channels = rv; + + ret = -E_MP4FF_BAD_SAMPLE_COUNT; + rv = mp4ff_num_samples(c->mp4ff, c->track); + if (rv <= 0) + goto close; + afhi->chunks_total = rv; + afhi->max_chunk_size = 0; + for (n = 0; n < afhi->chunks_total; n++) { + if (aac_afh_get_chunk(n, c, &buf, &sz) < 0) + break; + afhi->max_chunk_size = PARA_MAX((size_t)afhi->max_chunk_size, sz); } + + tmp = c->masc.sbr_present_flag == 1? 2048 : 1024; + afhi->seconds_total = tmp * afhi->chunks_total / afhi->frequency; + ms2tv(1000 * tmp / afhi->frequency, &afhi->chunk_tv); + + if (aac_afh_get_chunk(0, c, &buf, &sz) >= 0) + numbytes -= buf - map; + afhi->bitrate = 8 * numbytes / afhi->seconds_total / 1000; + _aac_afh_get_taginfo(c->mp4ff, &afhi->tags); + ret = 1; +close: + aac_afh_close(c); + return ret; } -static void aac_get_taginfo(char *buf, size_t buflen, struct afh_info *afhi) +static uint32_t aac_afh_meta_read_cb(void *user_data, void *dest, uint32_t want) { - int i; - uint64_t subsize; - char type[5]; - - for (i = 0; i + 24 < buflen; i++) { - char *p = buf + i; - if (p[0] != 'm' || p[1] != 'e' || p[2] != 't' || p[3] != 'a') - continue; - PARA_INFO_LOG("found metadata at offset %d\n", i); - i += 8; - p = buf + i; - i += read_atom_header(p, &subsize, type); - p = buf + i; - return read_meta(p, buflen - i, afhi); - } - PARA_INFO_LOG("no meta data\n"); + int fd = *(int *)user_data; + return read(fd, dest, want); } -static ssize_t aac_compute_chunk_table(struct afh_info *afhi, - char *map, size_t numbytes) +static uint32_t aac_afh_meta_seek_cb(void *user_data, uint64_t pos) { - int ret, i; - size_t sum = 0; - off_t skip; + int fd = *(int *)user_data; + return lseek(fd, pos, SEEK_SET); +} - ret = aac_find_stsz(map, numbytes, &skip); - if (ret < 0) - return ret; - afhi->chunks_total = ret; - PARA_DEBUG_LOG("sz table has %" PRIu32 " entries\n", afhi->chunks_total); - afhi->chunk_table = para_malloc((afhi->chunks_total + 1) * sizeof(size_t)); - for (i = 1; i <= afhi->chunks_total; i++) { - if (skip + 4 > numbytes) - break; - sum += read_u32_be(map + skip); - afhi->chunk_table[i] = sum; - skip += 4; -// if (i < 10 || i + 10 > afhi->chunks_total) -// PARA_DEBUG_LOG("offset #%d: %zu\n", i, afhi->chunk_table[i]); - } - return skip; +static uint32_t aac_afh_meta_write_cb(void *user_data, void *dest, uint32_t want) +{ + int fd = *(int *)user_data; + return write(fd, dest, want); } -static int aac_set_chunk_tv(struct afh_info *afhi, - mp4AudioSpecificConfig *mp4ASC, uint32_t *seconds) +static uint32_t aac_afh_meta_truncate_cb(void *user_data) { - float tmp = mp4ASC->sbr_present_flag == 1? 2047 : 1023; - struct timeval total; - long unsigned ms; - - if (!mp4ASC->samplingFrequency) - return -E_MP4ASC; - ms = 1000.0 * afhi->chunks_total * tmp / mp4ASC->samplingFrequency; - ms2tv(ms, &total); - tv_divide(afhi->chunks_total, &total, &afhi->chunk_tv); - PARA_INFO_LOG("%luHz, %lus (%" PRIu32 " x %lums)\n", - mp4ASC->samplingFrequency, ms / 1000, - afhi->chunks_total, tv2ms(&afhi->chunk_tv)); - if (ms < 1000) - return -E_MP4ASC; - *seconds = ms / 1000; - return 1; + int fd = *(int *)user_data; + off_t offset = lseek(fd, 0, SEEK_CUR); + return ftruncate(fd, offset); } -/* - * Init m4a file and write some tech data to given pointers. - */ -static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd, - struct afh_info *afhi) +static void replace_tag(mp4ff_tag_t *tag, const char *new_val, bool *found) { - int i; - size_t skip; - ssize_t ret; - unsigned long rate = 0, decoder_len; - unsigned char channels = 0; - mp4AudioSpecificConfig mp4ASC; - NeAACDecHandle handle = NULL; - - ret = aac_find_esds(map, numbytes, &skip, &decoder_len); - if (ret < 0) - goto out; - aac_get_taginfo(map, numbytes, afhi); - handle = aac_open(); - ret = -E_AAC_AFH_INIT; - if (NeAACDecInit(handle, (unsigned char *)map + skip, decoder_len, - &rate, &channels)) - goto out; - if (!channels) - goto out; - PARA_DEBUG_LOG("rate: %lu, channels: %d\n", rate, channels); - ret = -E_MP4ASC; - if (NeAACDecAudioSpecificConfig((unsigned char *)map + skip, - numbytes - skip, &mp4ASC)) - goto out; - if (!mp4ASC.samplingFrequency) - goto out; - ret = aac_compute_chunk_table(afhi, map, numbytes); - if (ret < 0) - goto out; - skip = ret; - ret = aac_set_chunk_tv(afhi, &mp4ASC, &afhi->seconds_total); - if (ret < 0) - goto out; - ret = aac_find_entry_point(map + skip, numbytes - skip, &skip); - if (ret < 0) - goto out; - afhi->chunk_table[0] = ret; - for (i = 1; i<= afhi->chunks_total; i++) - afhi->chunk_table[i] += ret; - afhi->channels = channels; - afhi->frequency = rate; - ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */ - ret += (channels * afhi->seconds_total * 500); /* avoid rounding error */ - afhi->bitrate = ret / (channels * afhi->seconds_total * 1000); - ret = 1; -out: - if (handle) - NeAACDecClose(handle); - return ret; + free(tag->value); + tag->value = para_strdup(new_val); + *found = true; } -static int aac_rewrite_tags(const char *map, size_t mapsize, - struct taginfo *tags, int fd, const char *filename) +static void add_tag(mp4ff_metadata_t *md, const char *item, const char *value) { - MP4FileHandle h; - const MP4Tags *mdata; - int ret = write_all(fd, map, mapsize); + md->tags[md->count].item = para_strdup(item); + md->tags[md->count].value = para_strdup(value); + md->count++; +} +static int aac_afh_rewrite_tags(const char *map, size_t mapsize, + struct taginfo *tags, int fd, __a_unused const char *filename) +{ + int ret, i; + int32_t rv; + mp4ff_metadata_t metadata; + mp4ff_t *mp4ff; + mp4ff_callback_t cb = { + .read = aac_afh_meta_read_cb, + .seek = aac_afh_meta_seek_cb, + .write = aac_afh_meta_write_cb, + .truncate = aac_afh_meta_truncate_cb, + .user_data = &fd + }; + bool found_artist = false, found_title = false, found_album = false, + found_year = false, found_comment = false; + + ret = write_all(fd, map, mapsize); if (ret < 0) return ret; lseek(fd, 0, SEEK_SET); - h = MP4Modify(filename, 0); - if (!h) { - PARA_ERROR_LOG("MP4Modify() failed, fd = %d\n", fd); - return -E_MP4V2; - } - mdata = MP4TagsAlloc(); - assert(mdata); - if (!MP4TagsFetch(mdata, h)) { - PARA_ERROR_LOG("MP4Tags_Fetch() failed\n"); - ret = -E_MP4V2; - goto close; - } - if (!MP4TagsSetAlbum(mdata, tags->album)) { - PARA_ERROR_LOG("Could not set album\n"); - ret = -E_MP4V2; - goto tags_free; - } - if (!MP4TagsSetArtist(mdata, tags->artist)) { - PARA_ERROR_LOG("Could not set album\n"); - ret = -E_MP4V2; - goto tags_free; - } - if (!MP4TagsSetComments(mdata, tags->comment)) { - PARA_ERROR_LOG("Could not set comment\n"); - ret = -E_MP4V2; - goto tags_free; - } - if (!MP4TagsSetName(mdata, tags->title)) { - PARA_ERROR_LOG("Could not set title\n"); - ret = -E_MP4V2; - goto tags_free; - } - if (!MP4TagsSetReleaseDate(mdata, tags->year)) { - PARA_ERROR_LOG("Could not set release date\n"); - ret = -E_MP4V2; - goto tags_free; - } + mp4ff = mp4ff_open_read_metaonly(&cb); + if (!mp4ff) + return -E_MP4FF_OPEN; - if (!MP4TagsStore(mdata, h)) { - PARA_ERROR_LOG("Could not store tags\n"); - ret = -E_MP4V2; - goto tags_free; + ret = -E_MP4FF_META_READ; + rv = mp4ff_meta_get_num_items(mp4ff); + if (rv < 0) + goto close; + metadata.count = rv; + PARA_NOTICE_LOG("%d metadata item(s) found\n", rv); + + metadata.tags = para_malloc((metadata.count + 5) * sizeof(mp4ff_tag_t)); + for (i = 0; i < metadata.count; i++) { + mp4ff_tag_t *tag = metadata.tags + i; + + ret = -E_MP4FF_META_READ; + if (mp4ff_meta_get_by_index(mp4ff, i, + &tag->item, &tag->value) < 0) + goto free_tags; + PARA_INFO_LOG("found: %s: %s\n", tag->item, tag->value); + if (!strcmp(tag->item, "artist")) + replace_tag(tag, tags->artist, &found_artist); + else if (!strcmp(tag->item, "title")) + replace_tag(tag, tags->title, &found_title); + else if (!strcmp(tag->item, "album")) + replace_tag(tag, tags->album, &found_album); + else if (!strcmp(tag->item, "date")) + replace_tag(tag, tags->year, &found_year); + else if (!strcmp(tag->item, "comment")) + replace_tag(tag, tags->comment, &found_comment); } + if (!found_artist) + add_tag(&metadata, "artist", tags->artist); + if (!found_title) + add_tag(&metadata, "title", tags->title); + if (!found_album) + add_tag(&metadata, "album", tags->album); + if (!found_year) + add_tag(&metadata, "date", tags->year); + if (!found_comment) + add_tag(&metadata, "comment", tags->comment); + ret = -E_MP4FF_META_WRITE; + if (mp4ff_meta_update(&cb, &metadata) < 0) + goto free_tags; ret = 1; -tags_free: - MP4TagsFree(mdata); +free_tags: + for (; i > 0; i--) { + free(metadata.tags[i - 1].item); + free(metadata.tags[i - 1].value); + } + free(metadata.tags); close: - MP4Close(h, 0); + mp4ff_close(mp4ff); return ret; } @@ -326,5 +346,8 @@ void aac_afh_init(struct audio_format_handler *afh) { afh->get_file_info = aac_get_file_info, afh->suffixes = aac_suffixes; - afh->rewrite_tags = aac_rewrite_tags; + afh->rewrite_tags = aac_afh_rewrite_tags; + afh->open = aac_afh_open; + afh->get_chunk = aac_afh_get_chunk; + afh->close = aac_afh_close; } diff --git a/aac_common.c b/aac_common.c deleted file mode 100644 index 812c742c..00000000 --- a/aac_common.c +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2006 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ -/* - * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker, - * Ahead Software AG - */ - -/** \file aac_common.c Common functions of aac_afh and aadcec. */ - -#include "para.h" -#include "aac.h" -#include "error.h" -#include "portable_io.h" - -/** - * Get a new libfaad decoder handle. - * - * \return The handle returned by NeAACDecOpen(). - */ -NeAACDecHandle aac_open(void) -{ - NeAACDecHandle h = NeAACDecOpen(); - NeAACDecConfigurationPtr c = NeAACDecGetCurrentConfiguration(h); - - c->defObjectType = LC; - c->outputFormat = FAAD_FMT_16BIT; - c->downMatrix = 0; - NeAACDecSetConfiguration(h, c); - return h; -} - -static unsigned long aac_read_decoder_length(char *buf, int *description_len) -{ - uint8_t b; - uint8_t numBytes = 0; - unsigned long length = 0; - - do { - b = buf[numBytes]; - numBytes++; - length = (length << 7) | (b & 0x7F); - } while - ((b & 0x80) && numBytes < 4); - *description_len = numBytes; - return length; -} - -/** - * search for the position and the length of the decoder configuration - * - * \param buf buffer to seach - * \param buflen length of \a buf - * \param skip Upon succesful return, this contains the offset in \a buf where - * the decoder config starts. - * \param decoder_length result pointer that is filled in with the length of - * the decoder configuration on success. - * - * \return positive on success, negative on errors - */ -int aac_find_esds(char *buf, size_t buflen, size_t *skip, - unsigned long *decoder_length) -{ - size_t i; - - for (i = 0; i + 4 < buflen; i++) { - char *p = buf + i; - int description_len; - - if (p[0] != 'e' || p[1] != 's' || p[2] != 'd' || p[3] != 's') - continue; - i += 8; - p = buf + i; - PARA_INFO_LOG("found esds@%zu, next: %x\n", i, (unsigned)*p); - if (*p == 3) - i += 8; - else - i += 6; - p = buf + i; - PARA_INFO_LOG("next: %x\n", (unsigned)*p); - if (*p != 4) - continue; - i += 18; - p = buf + i; - PARA_INFO_LOG("next: %x\n", (unsigned)*p); - if (*p != 5) - continue; - i++; - p = buf + i; - *decoder_length = aac_read_decoder_length(p, &description_len); - PARA_INFO_LOG("decoder length: %lu\n", *decoder_length); - i += description_len; - *skip = i; - return 1; - } - return -E_ESDS; -} - -/** - * search for the first entry in the stco table - * - * \param buf buffer to seach - * \param buflen length of \a buf - * \param skip Upon succesful return, this contains the number - * of bytes to skip from the input buffer. - * - * \return the position of the first entry in the table on success, - * -E_STCO on errors. - */ -ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip) -{ - ssize_t ret; - size_t i; - - for (i = 0; i + 20 < buflen; i++) { - char *p = buf + i; - - if (p[0] != 's' || p[1] != 't' || p[2] != 'c' || p[3] != 'o') - continue; - PARA_INFO_LOG("found stco@%zu\n", i); - i += 12; - ret = read_u32_be(buf + i); /* first offset */ - i += 4; - PARA_INFO_LOG("entry point: %zd\n", ret); - *skip = i; - return ret; - } - PARA_WARNING_LOG("stco not found, buflen: %zu\n", buflen); - return -E_STCO; -} diff --git a/aacdec_filter.c b/aacdec_filter.c index 5725ce04..26d5f652 100644 --- a/aacdec_filter.c +++ b/aacdec_filter.c @@ -11,16 +11,16 @@ /** \file aacdec_filter.c paraslash's aac (m4a) decoder. */ #include +#include #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 @@ -33,21 +33,12 @@ struct private_aacdec_data { /** the return value of aac_open */ NeAACDecHandle handle; - /** info about the currently decoded frame */ - NeAACDecFrameInfo frame_info; /** whether this instance of the aac decoder is already initialized */ - int initialized; - /** - * return value of aac_find_esds(). Used to call the right aacdec - * init function - */ - unsigned long decoder_length; + bool initialized; /** number of times the decoder returned an error */ unsigned error_count; /** number of bytes already consumed from the imput stream */ size_t consumed_total; - /** return value of aac_find_entry_point */ - size_t entry; /** The number of channels of the current stream. */ unsigned int channels; /** Current sample rate in Hz. */ @@ -64,11 +55,18 @@ static int aacdec_execute(struct btr_node *btrn, const char *cmd, char **result) static void aacdec_open(struct filter_node *fn) { + NeAACDecConfigurationPtr c; struct private_aacdec_data *padd = para_calloc(sizeof(*padd)); + padd->handle = NeAACDecOpen(); + c = NeAACDecGetCurrentConfiguration(padd->handle); + c->defObjectType = LC; + c->outputFormat = FAAD_FMT_16BIT; + c->downMatrix = 0; + NeAACDecSetConfiguration(padd->handle, c); + fn->private_data = padd; fn->min_iqs = 2048; - padd->handle = aac_open(); } static void aacdec_close(struct filter_node *fn) @@ -86,9 +84,9 @@ static int aacdec_post_select(__a_unused struct sched *s, void *context) struct btr_node *btrn = fn->btrn; struct private_aacdec_data *padd = fn->private_data; int i, ret; - char *p, *inbuf, *outbuffer; - char *btr_buf; - size_t len, skip, consumed, loaded; + char *inbuf, *outbuf, *btrbuf; + size_t len, consumed, loaded = 0; + NeAACDecFrameInfo frame_info; next_buffer: ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); @@ -103,121 +101,68 @@ next_buffer: if (!padd->initialized) { unsigned long rate = 0; unsigned char channels = 0; - ret = aac_find_esds(inbuf, len, &skip, &padd->decoder_length); + ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf, + len, &rate, &channels); + PARA_INFO_LOG("decoder init: %d\n", ret); if (ret < 0) { - PARA_INFO_LOG("%s\n", para_strerror(-ret)); - ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf, - len, &rate, &channels); - PARA_INFO_LOG("decoder init: %d\n", ret); - if (ret < 0) { - ret = -E_AACDEC_INIT; - goto out; - } - consumed = ret; - } else { - PARA_INFO_LOG("decoder len: %lu\n", - padd->decoder_length); - consumed += skip; - p = inbuf + consumed; ret = -E_AACDEC_INIT; - if (NeAACDecInit2(padd->handle, (unsigned char *)p, - padd->decoder_length, &rate, - &channels) != 0) - goto out; + goto err; } + consumed = ret; padd->sample_rate = rate; padd->channels = channels; PARA_INFO_LOG("rate: %u, channels: %u\n", padd->sample_rate, padd->channels); - padd->initialized = 1; + padd->initialized = true; } - if (padd->decoder_length > 0) { - consumed = 0; - if (!padd->entry) { - ret = aac_find_entry_point(inbuf + consumed, - len - consumed, &skip); - if (ret < 0) { - ret = len; - goto out; - } - consumed += skip; - padd->entry = ret; - PARA_INFO_LOG("entry: %zu\n", padd->entry); - } - ret = len; - if (padd->consumed_total + len < padd->entry) - goto out; - if (padd->consumed_total < padd->entry) - consumed = padd->entry - padd->consumed_total; - } - for (; consumed < len; consumed++) - if ((inbuf[consumed] & 0xfe) == 0x20) - break; if (consumed >= len) goto success; - p = inbuf + consumed; //PARA_CRIT_LOG("consumed: %zu (%zu + %zu), have: %zu\n", padd->consumed_total + consumed, // padd->consumed_total, consumed, len - consumed); - outbuffer = NeAACDecDecode(padd->handle, &padd->frame_info, - (unsigned char *)p, len - consumed); - if (padd->frame_info.error) { - int err = padd->frame_info.error; + outbuf = NeAACDecDecode(padd->handle, &frame_info, + (unsigned char *)inbuf + consumed, len - consumed); + if (frame_info.error) { + int err = frame_info.error; ret = -E_AAC_DECODE; if (padd->error_count++ > MAX_ERRORS) goto err; - /* Suppress non-fatal bitstream error message at BOF/EOF */ - if (len < fn->min_iqs || padd->consumed_total == 0) { - consumed = len; - goto success; - } - PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage(err)); - PARA_ERROR_LOG("consumed: %zu + %zu + %lu\n", + PARA_NOTICE_LOG("error #%u: (%s)\n", padd->error_count, + NeAACDecGetErrorMessage(err)); + PARA_NOTICE_LOG("consumed (total, buffer, frame): " + "%zu, %zu, %lu\n", padd->consumed_total, consumed, - padd->frame_info.bytesconsumed); - if (consumed < len) - consumed++; /* catch 21 */ + frame_info.bytesconsumed); + consumed++; /* just eat one byte and hope for the best */ goto success; } padd->error_count = 0; - //PARA_CRIT_LOG("decoder ate %lu\n", padd->frame_info.bytesconsumed); - consumed += padd->frame_info.bytesconsumed; - ret = consumed; - if (!padd->frame_info.samples) - goto out; - btr_buf = para_malloc(2 * padd->frame_info.samples); - loaded = 0; - for (i = 0; i < padd->frame_info.samples; i++) { - short sh = ((short *)outbuffer)[i]; - write_int16_host_endian(btr_buf + loaded, sh); + //PARA_CRIT_LOG("decoder ate %lu\n", frame_info.bytesconsumed); + consumed += frame_info.bytesconsumed; + if (!frame_info.samples) + goto success; + btrbuf = para_malloc(2 * frame_info.samples); + for (i = 0; i < frame_info.samples; i++) { + short sh = ((short *)outbuf)[i]; + write_int16_host_endian(btrbuf + loaded, sh); loaded += 2; } - btr_add_output(btr_buf, loaded, btrn); + btr_add_output(btrbuf, loaded, btrn); success: - ret = consumed; -out: - if (ret >= 0) { - padd->consumed_total += ret; - btr_consume(btrn, ret); + btr_consume(btrn, consumed); + padd->consumed_total += consumed; + if (loaded == 0) goto next_buffer; - } + return 1; err: assert(ret < 0); btr_remove_node(&fn->btrn); return ret; } -/** - * The init function of the aacdec filter. - * - * \param f Pointer to the filter struct to initialize. - * - * \sa filter::init - */ -void aacdec_filter_init(struct filter *f) -{ - f->open = aacdec_open; - f->close = aacdec_close; - f->pre_select = generic_filter_pre_select; - f->post_select = aacdec_post_select; - f->execute = aacdec_execute; -} +const struct filter lsg_filter_cmd_com_aacdec_user_data = { + .open = aacdec_open, + .close = aacdec_close, + .pre_select = generic_filter_pre_select, + .post_select = aacdec_post_select, + .execute = aacdec_execute +}; diff --git a/acl.c b/acl.c index 560ff999..62677711 100644 --- a/acl.c +++ b/acl.c @@ -81,7 +81,7 @@ no_match: * \param addr The address to add. * \param netmask The netmask to use for this entry. */ -static void acl_add_entry(struct list_head *acl, char *addr, int netmask) +void acl_add_entry(struct list_head *acl, char *addr, int netmask) { struct access_info *ai = para_malloc(sizeof(struct access_info)); @@ -139,27 +139,6 @@ char *acl_get_contents(struct list_head *acl) return ret; } -/** - * Initialize an access control list. - * - * \param acl The list to initialize. - * \param acl_info An array of strings of the form ip/netmask. - * \param num The number of strings in \a acl_info. - */ -void acl_init(struct list_head *acl, char * const *acl_info, int num) -{ - char addr[16]; - int mask, i; - - INIT_LIST_HEAD(acl); - for (i = 0; i < num; i++) - if (parse_cidr(acl_info[i], addr, sizeof(addr), &mask) == NULL) - PARA_CRIT_LOG("ACL syntax error: %s, ignoring\n", - acl_info[i]); - else - acl_add_entry(acl, addr, mask); -} - /** * Check whether the peer name of a given fd is allowed by an acl. * diff --git a/acl.h b/acl.h index 5bfa39f2..91efa600 100644 --- a/acl.h +++ b/acl.h @@ -6,7 +6,7 @@ /** \file acl.h Exported functions of acl.c. */ -void acl_init(struct list_head *acl, char * const *acl_info, int num); +void acl_add_entry(struct list_head *acl, char *addr, int netmask); char *acl_get_contents(struct list_head *acl); int acl_check_access(int fd, struct list_head *acl, int default_deny); void acl_allow(char *addr, int mask, struct list_head *acl, int default_deny); diff --git a/afh.c b/afh.c index 36c432e5..9d40caeb 100644 --- a/afh.c +++ b/afh.c @@ -7,20 +7,26 @@ /** \file afh.c Paraslash's standalone audio format handler tool. */ #include +#include +#include "afh.lsg.h" #include "para.h" #include "string.h" -#include "afh.cmdline.h" #include "fd.h" #include "afh.h" #include "error.h" #include "version.h" -#include "ggo.h" /** Array of error strings. */ DEFINE_PARA_ERRLIST; -static struct afh_args_info conf; +static struct lls_parse_result *lpr; + +#define CMD_PTR (lls_cmd(0, afh_suite)) +#define OPT_RESULT(_name) (lls_opt_result(LSG_AFH_PARA_AFH_OPT_ ## _name, lpr)) +#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name))) +#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name))) +#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name))) static int loglevel; INIT_STDERR_LOGGING(loglevel) @@ -37,34 +43,38 @@ static int rewrite_tags(const char *name, int input_fd, void *map, struct taginfo *tags = &afhi->tags; bool modified = false; char *tmp_name; + const char *arg; int output_fd = -1, ret; struct stat sb; - if (tag_needs_update(conf.year_given, tags->year, conf.year_arg)) { + arg = OPT_STRING_VAL(YEAR); + if (tag_needs_update(OPT_GIVEN(YEAR), tags->year, arg)) { free(tags->year); - tags->year = para_strdup(conf.year_arg); + tags->year = para_strdup(arg); modified = true; } - if (tag_needs_update(conf.title_given, tags->title, conf.title_arg)) { + arg = OPT_STRING_VAL(TITLE); + if (tag_needs_update(OPT_GIVEN(TITLE), tags->title, arg)) { free(tags->title); - tags->title = para_strdup(conf.title_arg); + tags->title = para_strdup(arg); modified = true; } - if (tag_needs_update(conf.artist_given, tags->artist, - conf.artist_arg)) { + arg = OPT_STRING_VAL(ARTIST); + if (tag_needs_update(OPT_GIVEN(ARTIST), tags->artist, arg)) { free(tags->artist); - tags->artist = para_strdup(conf.artist_arg); + tags->artist = para_strdup(arg); modified = true; } - if (tag_needs_update(conf.album_given, tags->album, conf.album_arg)) { + arg = OPT_STRING_VAL(ALBUM); + if (tag_needs_update(OPT_GIVEN(ALBUM), tags->album, arg)) { free(tags->album); - tags->album = para_strdup(conf.album_arg); + tags->album = para_strdup(arg); modified = true; } - if (tag_needs_update(conf.comment_given, tags->comment, - conf.comment_arg)) { + arg = OPT_STRING_VAL(COMMENT); + if (tag_needs_update(OPT_GIVEN(COMMENT), tags->comment, arg)) { free(tags->comment); - tags->comment = para_strdup(conf.comment_arg); + tags->comment = para_strdup(arg); modified = true; } if (!modified) { @@ -99,7 +109,7 @@ static int rewrite_tags(const char *name, int input_fd, void *map, tmp_name); if (ret < 0) goto out; - if (conf.backup_given) { + if (OPT_GIVEN(BACKUP)) { char *backup_name = make_message("%s~", name); ret = xrename(name, backup_name); free(backup_name); @@ -125,39 +135,53 @@ static void print_info(int audio_format_num, struct afh_info *afhi) free(msg); } -static void print_chunk_table(struct afh_info *afhi) +static void print_chunk_table(struct afh_info *afhi, int audio_format_id, + const void *map, size_t mapsize) { - int i; + int i, ret; + void *ctx = NULL; - if (conf.parser_friendly_given) { - printf("chunk_table: "); - for (i = 0; i <= afhi->chunks_total; i++) - printf("%u ", afhi->chunk_table[i]); - printf("\n"); - return; - } - for (i = 1; i <= afhi->chunks_total; i++) { + for (i = 0; i < afhi->chunks_total; i++) { struct timeval tv; long unsigned from, to; - tv_scale(i - 1, &afhi->chunk_tv, &tv); - from = tv2ms(&tv); + const char *buf; + size_t len; tv_scale(i, &afhi->chunk_tv, &tv); + from = tv2ms(&tv); + tv_scale(i + 1, &afhi->chunk_tv, &tv); to = tv2ms(&tv); - printf("%d [%lu.%03lu - %lu.%03lu] %u - %u (%u)\n", i - 1, - from / 1000, from % 1000, to / 1000, to % 1000, - afhi->chunk_table[i - 1], afhi->chunk_table[i], - afhi->chunk_table[i] - afhi->chunk_table[i - 1]); + ret = afh_get_chunk(i, afhi, audio_format_id, map, mapsize, + &buf, &len, &ctx); + if (ret < 0) { + PARA_ERROR_LOG("fatal: chunk %d: %s\n", i, + para_strerror(-ret)); + return; + } + if (!OPT_GIVEN(PARSER_FRIENDLY)) + printf("%d [%lu.%03lu - %lu.%03lu] ", i, from / 1000, + from % 1000, to / 1000, to % 1000); + printf("%td - %td", buf - (const char *)map, + buf + len - (const char *)map); + if (!OPT_GIVEN(PARSER_FRIENDLY)) + printf(" (%zu)", len); + printf("\n"); } + afh_close(ctx, audio_format_id); } -__noreturn static void print_help_and_die(void) +static void handle_help_flags(void) { - struct ggo_help h = DEFINE_GGO_HELP(afh); - int d = conf.detailed_help_given; - unsigned flags = d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS; + char *help; - ggo_print_help(&h, flags); - printf("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS); + if (OPT_GIVEN(DETAILED_HELP)) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP) || lls_num_inputs(lpr) == 0) + help = lls_short_help(CMD_PTR); + else + return; + printf("%s", help); + free(help); + printf("Supported audio formats\n %s\n", AUDIO_FORMAT_HANDLERS); exit(EXIT_SUCCESS); } @@ -175,34 +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) - print_chunk_table(&afhi); - printf("\n"); + if (OPT_GIVEN(CHUNK_TABLE)) + print_chunk_table(&afhi, audio_format_num, + audio_file_data, audio_file_size); } clear_afhi(&afhi); } @@ -214,7 +241,9 @@ int main(int argc, char **argv) break; } out: + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); if (ret < 0) - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/afh.h b/afh.h index a6f9c500..6dc5a3fc 100644 --- a/afh.h +++ b/afh.h @@ -40,6 +40,8 @@ struct afh_info { * the current audio file. */ uint32_t *chunk_table; + /** Size of the largest chunk, introduced in v0.6.0. */ + uint32_t max_chunk_size; /** Period of time between sending data chunks. */ struct timeval chunk_tv; /** @@ -64,7 +66,10 @@ struct audio_file_data { int fd; /** Vss needs this for streaming. */ struct afh_info afhi; - /** Size of the largest chunk. */ + /** + * Size of the largest chunk. Superseded by afhi->max_chunk_size. May + * be removed after v0.6.1. + */ uint32_t max_chunk_size; /** Needed to get the audio file header. */ uint8_t audio_format_id; @@ -101,9 +106,29 @@ struct audio_format_handler { * \sa struct afh_info */ int (*get_file_info)(char *map, size_t numbytes, int fd, - struct afh_info *afi); + struct afh_info *afhi); /** Optional, used for header-rewriting. See \ref afh_get_header(). */ void (*get_header)(void *map, size_t mapsize, char **buf, size_t *len); + /** + * An audio format handler may signify support for dynamic chunks by + * defining ->get_chunk below. In this case the vss calls ->open() at + * BOS, ->get_chunk() for each chunk while streaming, and ->close() at + * EOS. The chunk table is not accessed at all. + * + * The function may return its (opaque) context through the last + * argument. The returned pointer is passed to subsequent calls to + * ->get_chunk() and ->close(). + */ + int (*open)(const void *map, size_t mapsize, void **afh_context); + /** + * Return a reference to one chunk. The returned pointer points to a + * portion of the memory mapped audio file. The caller must not call + * free() on it. + */ + int (*get_chunk)(long unsigned chunk_num, void *afh_context, + const char **buf, size_t *len); + /** Deallocate the resources occupied by ->open(). */ + void (*close)(void *afh_context); /** * Write audio file with altered tags, optional. * @@ -119,10 +144,12 @@ int guess_audio_format(const char *name); int compute_afhi(const char *path, char *data, size_t size, int fd, struct afh_info *afhi); const char *audio_format_name(int); -void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, - void *map, const char **buf, size_t *len); +__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, + uint8_t audio_format_id, const void *map, size_t mapsize, + const char **buf, size_t *len, void **afh_context); +void afh_close(void *afh_context, uint8_t audio_format_id); int32_t afh_get_start_chunk(int32_t approx_chunk_num, - const struct afh_info *afhi); + const struct afh_info *afhi, uint8_t audio_format_id); void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id, void *map, size_t mapsize, char **buf, size_t *len); void afh_free_header(char *header_buf, uint8_t audio_format_id); @@ -130,3 +157,5 @@ void clear_afhi(struct afh_info *afhi); unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **result); int afh_rewrite_tags(int audio_format_id, void *map, size_t mapsize, struct taginfo *tags, int output_fd, const char *filename); +void set_max_chunk_size(struct afh_info *afhi); +bool afh_supports_dynamic_chunks(int audio_format_id); diff --git a/afh_common.c b/afh_common.c index dfbf7513..6feb7c35 100644 --- a/afh_common.c +++ b/afh_common.c @@ -48,7 +48,7 @@ static struct audio_format_handler afl[] = { }, { .name = "aac", -#if defined(HAVE_MP4V2) +#if defined(HAVE_FAAD) .init = aac_afh_init, #endif }, @@ -88,7 +88,6 @@ static inline int next_audio_format(int format) if (afl[format].init) return format; } - } /** Iterate over each supported audio format. */ @@ -109,6 +108,22 @@ void afh_init(void) } } +/** + * Tell whether an audio format handler provides chunk tables. + * + * Each audio format handler either provides a chunk table or supports dynamic + * chunks. + * + * \param audio_format_id Offset in the afl array. + * + * \return True if dynamic chunks are supported, false if the audio format + * handler provides chunk tables. + */ +bool afh_supports_dynamic_chunks(int audio_format_id) +{ + return afl[audio_format_id].get_chunk; +} + /** * Guess the audio format judging from filename. * @@ -261,21 +276,73 @@ static inline size_t get_chunk_len(long unsigned chunk_num, /** * Get one chunk of audio data. * + * This implicitly calls the ->open method of the audio format handler at the + * first call. + * * \param chunk_num The number of the chunk to get. * \param afhi Describes the audio file. + * \param audio_format_id Determines the afh. * \param map The memory mapped audio file. + * \param mapsize Passed to the afh's ->open() method. * \param buf Result pointer. * \param len The length of the chunk in bytes. + * \param afh_context Value/result, determines whether ->open() is called. * * Upon return, \a buf will point so memory inside \a map. The returned buffer * must therefore not be freed by the caller. + * + * \return Standard. + */ +__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, + uint8_t audio_format_id, const void *map, size_t mapsize, + const char **buf, size_t *len, void **afh_context) +{ + struct audio_format_handler *afh = afl + audio_format_id; + + if (afh_supports_dynamic_chunks(audio_format_id)) { + int ret; + + if (!*afh_context) { + ret = afh->open(map, mapsize, afh_context); + if (ret < 0) + return ret; + } + ret = afl[audio_format_id].get_chunk(chunk_num, *afh_context, + buf, len); + if (ret < 0) { + afh->close(*afh_context); + *afh_context = NULL; + } + return ret; + } else { + size_t pos = afhi->chunk_table[chunk_num]; + *buf = map + pos; + *len = get_chunk_len(chunk_num, afhi); + return 0; + } +} + +/** + * Deallocate resources allocated due to dynamic chunk handling. + * + * This function should be called if afh_get_chunk() was called at least once. + * It is OK to call it even for audio formats which do not support dynamic + * chunks, in which case the function does nothing. + * + * \param afh_context As returned from the ->open method of the afh. + * \param audio_format_id Determines the afh. */ -void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, - void *map, const char **buf, size_t *len) +void afh_close(void *afh_context, uint8_t audio_format_id) { - size_t pos = afhi->chunk_table[chunk_num]; - *buf = map + pos; - *len = get_chunk_len(chunk_num, afhi); + struct audio_format_handler *afh = afl + audio_format_id; + + if (!afh_supports_dynamic_chunks(audio_format_id)) + return; + if (!afh->close) + return; + if (!afh_context) + return; + afh->close(afh_context); } /** @@ -283,16 +350,22 @@ void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, * * \param approx_chunk_num Upper bound for the chunk number to return. * \param afhi Needed for the chunk table. + * \param audio_format_id Determines the afh. * - * \return The first non-empty chunk <= \a approx_chunk_num. + * \return For audio format handlers which support dynamic chunks, the function + * returns the given chunk number. Otherwise it returns the first non-empty + * chunk <= \a approx_chunk_num. * * \sa \ref afh_get_chunk(). */ int32_t afh_get_start_chunk(int32_t approx_chunk_num, - const struct afh_info *afhi) + const struct afh_info *afhi, uint8_t audio_format_id) { int32_t k; + if (afh_supports_dynamic_chunks(audio_format_id)) + return approx_chunk_num; + for (k = PARA_MAX(0, approx_chunk_num); k >= 0; k--) if (get_chunk_len(k, afhi) > 0) return k; @@ -374,6 +447,7 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re "%s: %" PRIu32 "\n" /* seconds total */ "%s: %lu: %lu\n" /* chunk time */ "%s: %" PRIu32 "\n" /* num chunks */ + "%s: %" PRIu32 "\n" /* max chunk size */ "%s: %s\n" /* techinfo */ "%s: %s\n" /* artist */ "%s: %s\n" /* title */ @@ -388,6 +462,7 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re status_item_list[SI_CHUNK_TIME], (long unsigned)afhi->chunk_tv.tv_sec, (long unsigned)afhi->chunk_tv.tv_usec, status_item_list[SI_NUM_CHUNKS], afhi->chunks_total, + status_item_list[SI_MAX_CHUNK_SIZE], afhi->max_chunk_size, status_item_list[SI_TECHINFO], afhi->techinfo? afhi->techinfo : "", status_item_list[SI_ARTIST], afhi->tags.artist? afhi->tags.artist : "", status_item_list[SI_TITLE], afhi->tags.title? afhi->tags.title : "", @@ -397,6 +472,37 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re ); } +/** + * Determine the maximal chunk size by investigating the chunk table. + * + * \param afhi Value/result. + * + * This function iterates over the chunk table and sets ->max_chunk_size + * accordingly. The function exists only for backward compatibility since as of + * version 0.6.0, para_server stores the maximal chunk size in its database. + * This function is only called if the database value is zero, indicating that + * the file was added by an older server version. + */ +void set_max_chunk_size(struct afh_info *afhi) +{ + uint32_t n, max = 0, old = 0; + + for (n = 0; n <= afhi->chunks_total; n++) { + uint32_t val = afhi->chunk_table[n]; + /* + * If the first chunk is the header, do not consider it for the + * calculation of the largest chunk size. + */ + if (n == 0 || (n == 1 && afhi->header_len > 0)) { + old = val; + continue; + } + max = PARA_MAX(max, val - old); + old = val; + } + afhi->max_chunk_size = max; +} + /** * Create a copy of the given file with altered meta tags. * diff --git a/afh_recv.c b/afh_recv.c index 28d8f398..9d6effe1 100644 --- a/afh_recv.c +++ b/afh_recv.c @@ -8,15 +8,15 @@ #include #include +#include +#include "recv_cmd.lsg.h" #include "para.h" #include "error.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "recv.h" -#include "afh_recv.cmdline.h" #include "string.h" #include "fd.h" #include "afh.h" @@ -31,6 +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) @@ -58,47 +59,33 @@ 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; @@ -106,23 +93,23 @@ static int afh_recv_open(struct receiver_node *rn) 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->audio_format_num); else - pard->first_chunk = afh_get_start_chunk( - afhi->chunks_total + conf->begin_chunk_arg, - &pard->afhi); - if (conf->end_chunk_given) { + pard->first_chunk = afh_get_start_chunk(afhi->chunks_total + bc, + &pard->afhi, pard->audio_format_num); + if (lls_opt_given(r_e)) { + int32_t ec = lls_int32_val(0, r_e); ret = -ERRNO_TO_PARA_ERROR(EINVAL); - if (PARA_ABS(conf->end_chunk_arg) > afhi->chunks_total) + 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); @@ -150,6 +137,7 @@ static void afh_recv_close(struct receiver_node *rn) clear_afhi(&pard->afhi); para_munmap(pard->map, pard->map_size); close(pard->fd); + afh_close(pard->afh_context, pard->audio_format_num); freep(&rn->private_data); } @@ -158,13 +146,14 @@ static void afh_recv_pre_select(struct sched *s, void *context) struct receiver_node *rn = context; struct private_afh_recv_data *pard = rn->private_data; struct afh_info *afhi = &pard->afhi; - struct afh_recv_args_info *conf = rn->conf; + struct lls_parse_result *lpr = rn->lpr; struct timeval chunk_time; int state = generic_recv_pre_select(s, rn); + unsigned j_given = RECV_CMD_OPT_GIVEN(AFH, JUST_IN_TIME, lpr); if (state <= 0) return; - if (!conf->just_in_time_given) { + if (!j_given) { sched_min_delay(s); return; } @@ -176,20 +165,22 @@ static void afh_recv_pre_select(struct sched *s, void *context) static int afh_recv_post_select(__a_unused struct sched *s, void *context) { struct receiver_node *rn = context; - struct afh_recv_args_info *conf = rn->conf; + struct lls_parse_result *lpr = rn->lpr; struct private_afh_recv_data *pard = rn->private_data; struct btr_node *btrn = rn->btrn; struct afh_info *afhi = &pard->afhi; int ret; char *buf; - const char *start, *end; + const char *start; size_t size; struct timeval chunk_time; + unsigned j_given = RECV_CMD_OPT_GIVEN(AFH, JUST_IN_TIME, lpr); + unsigned H_given = RECV_CMD_OPT_GIVEN(AFH, NO_HEADER, lpr); ret = btr_node_status(btrn, 0, BTR_NT_ROOT); if (ret <= 0) goto out; - if (pard->first_chunk > 0 && !conf->no_header_given) { + if (pard->first_chunk > 0 && !H_given) { char *header; afh_get_header(afhi, pard->audio_format_num, pard->map, pard->map_size, &header, &size); @@ -201,12 +192,17 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context) afh_free_header(header, pard->audio_format_num); } } - if (!conf->just_in_time_given) { - afh_get_chunk(pard->first_chunk, afhi, pard->map, &start, &size); - afh_get_chunk(pard->last_chunk, afhi, pard->map, &end, &size); - end += size; - PARA_INFO_LOG("adding %td bytes\n", end - start); - btr_add_output_dont_free(start, end - start, btrn); + if (!j_given) { + long unsigned n; + for (n = pard->first_chunk; n < pard->last_chunk; n++) { + ret = afh_get_chunk(n, afhi, pard->audio_format_num, + pard->map, pard->map_size, &start, &size, + &pard->afh_context); + if (ret < 0) + goto out; + PARA_INFO_LOG("adding %zu bytes\n", size); + btr_add_output_dont_free(start, size, btrn); + } ret = -E_RECV_EOF; goto out; } @@ -219,7 +215,12 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context) if (ret > 0) goto out; } - afh_get_chunk(pard->current_chunk, afhi, pard->map, &start, &size); + ret = afh_get_chunk(pard->current_chunk, afhi, + pard->audio_format_num, pard->map, + pard->map_size, &start, &size, + &pard->afh_context); + if (ret < 0) + goto out; PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk); btr_add_output_dont_free(start, size, btrn); if (pard->current_chunk >= pard->last_chunk) { @@ -236,26 +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 --git a/afs.c b/afs.c index 0946b6df..75b82c21 100644 --- a/afs.c +++ b/afs.c @@ -12,11 +12,14 @@ #include #include #include +#include #include #include #include +#include -#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 @@ int send_callback_request(afs_callback *f, struct osl_object *query, *(uint32_t *)buf = afs_socket_cookie; *(int *)(buf + sizeof(afs_socket_cookie)) = query_shmid; - ret = connect_local_socket(conf.afs_socket_arg); + ret = connect_local_socket(OPT_STRING_VAL(AFS_SOCKET)); if (ret < 0) goto out; fd = ret; @@ -280,73 +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) @@ -354,30 +317,37 @@ static int action_if_pattern_matches(struct osl_row *row, void *data) struct pattern_match_data *pmd = data; struct osl_object name_obj; const char *p, *name; - int ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num, &name_obj)); - const char *pattern_txt = (const char *)pmd->patterns.data; + int i, ret; + ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num, + &name_obj)); if (ret < 0) return ret; name = (char *)name_obj.data; if ((!name || !*name) && (pmd->pm_flags & PM_SKIP_EMPTY_NAME)) return 1; - if (pmd->patterns.size == 0 && - (pmd->pm_flags & PM_NO_PATTERN_MATCHES_EVERYTHING)) { - pmd->num_matches++; - return pmd->action(pmd->table, row, name, pmd->data); + if (lls_num_inputs(pmd->lpr) == 0) { + if (pmd->pm_flags & PM_NO_PATTERN_MATCHES_EVERYTHING) { + pmd->num_matches++; + return pmd->action(pmd->table, row, name, pmd->data); + } } - for (p = pattern_txt; p < pattern_txt + pmd->patterns.size; - p += strlen(p) + 1) { + i = pmd->input_skip; + for (;;) { + if (i >= lls_num_inputs(pmd->lpr)) + break; + p = lls_input(i, pmd->lpr); ret = fnmatch(p, name, pmd->fnmatch_flags); - if (ret == FNM_NOMATCH) - continue; - if (ret) - return -E_FNMATCH; - ret = pmd->action(pmd->table, row, name, pmd->data); - if (ret >= 0) - pmd->num_matches++; - return ret; + if (ret != FNM_NOMATCH) { + if (ret != 0) + return -E_FNMATCH; + ret = pmd->action(pmd->table, row, name, pmd->data); + if (ret >= 0) + pmd->num_matches++; + return ret; + + } + i++; } return 1; } @@ -590,14 +560,17 @@ static void flush_and_free_pb(struct para_buffer *pb) static int com_select_callback(struct afs_callback_arg *aca) { - const char *arg = aca->query.data; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT); + const char *arg; int num_admissible, ret; + 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(); @@ -622,22 +595,26 @@ static int com_select_callback(struct afs_callback_arg *aca) 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 */ @@ -646,18 +623,13 @@ static void init_admissible_files(char *arg) 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, @@ -678,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( @@ -888,7 +860,7 @@ static int execute_server_command(fd_set *rfds) return ret; buf[n] = '\0'; if (strcmp(buf, "new")) - return -E_BAD_CMD; + return -ERRNO_TO_PARA_ERROR(EINVAL); return open_next_audio_file(); } @@ -1020,7 +992,7 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd) 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; @@ -1070,23 +1042,24 @@ out: return ret; } -int com_init(struct command_context *cc) +static int com_init(struct command_context *cc, struct lls_parse_result *lpr) { int i, j, ret; uint32_t table_mask = (1 << (NUM_AFS_TABLES + 1)) - 1; struct osl_object query = {.data = &table_mask, .size = sizeof(table_mask)}; + unsigned num_inputs = lls_num_inputs(lpr); ret = make_database_dir(); if (ret < 0) return ret; - if (cc->argc != 1) { + if (num_inputs > 0) { table_mask = 0; - for (i = 1; i < cc->argc; i++) { + for (i = 0; i < num_inputs; i++) { for (j = 0; j < NUM_AFS_TABLES; j++) { struct afs_table *t = &afs_tables[j]; - if (strcmp(cc->argv[i], t->name)) + if (strcmp(lls_input(i, lpr), t->name)) continue; table_mask |= (1 << j); break; @@ -1098,77 +1071,37 @@ int com_init(struct command_context *cc) return send_callback_request(com_init_callback, &query, afs_cb_result_handler, cc); } +EXPORT_SERVER_CMD_HANDLER(init); -/** - * Flags for the check command. - * - * \sa com_check(). - */ -enum com_check_flags { - /** Check the audio file table. */ - CHECK_AFT = 1, - /** Check the mood table. */ - CHECK_MOODS = 2, - /** Check the playlist table. */ - CHECK_PLAYLISTS = 4, - /** Check the attribute table against the audio file table. */ - CHECK_ATTS = 8 -}; - -int com_check(struct command_context *cc) +static int com_check(struct command_context *cc, struct lls_parse_result *lpr) { - unsigned flags = 0; - int i, ret; + const struct lls_opt_result *r_a = SERVER_CMD_OPT_RESULT(CHECK, AFT, lpr); + const struct lls_opt_result *r_A = SERVER_CMD_OPT_RESULT(CHECK, ATTRIBUTE, lpr); + const struct lls_opt_result *r_m = SERVER_CMD_OPT_RESULT(CHECK, MOOD, lpr); + const struct lls_opt_result *r_p = SERVER_CMD_OPT_RESULT(CHECK, PLAYLIST, lpr); + bool noopt = !lls_opt_given(r_a) && !lls_opt_given(r_A) + && !lls_opt_given(r_m) && !lls_opt_given(r_p); + int ret; - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-a")) { - flags |= CHECK_AFT; - continue; - } - if (!strcmp(arg, "-A")) { - flags |= CHECK_ATTS; - continue; - } - if (!strcmp(arg, "-p")) { - flags |= CHECK_PLAYLISTS; - continue; - } - if (!strcmp(arg, "-m")) { - flags |= CHECK_MOODS; - continue; - } - return -E_AFS_SYNTAX; - } - if (i < cc->argc) - return -E_AFS_SYNTAX; - if (!flags) - flags = ~0U; - if (flags & CHECK_AFT) { + if (noopt || lls_opt_given(r_a)) { ret = send_callback_request(aft_check_callback, NULL, afs_cb_result_handler, cc); if (ret < 0) return ret; } - if (flags & CHECK_ATTS) { + if (noopt || lls_opt_given(r_A)) { ret = send_callback_request(attribute_check_callback, NULL, afs_cb_result_handler, cc); if (ret < 0) return ret; } - if (flags & CHECK_PLAYLISTS) { + if (noopt || lls_opt_given(r_p)) { ret = send_callback_request(playlist_check_callback, NULL, afs_cb_result_handler, cc); if (ret < 0) return ret; } - if (flags & CHECK_MOODS) { + if (noopt || lls_opt_given(r_m)) { ret = send_callback_request(mood_check_callback, NULL, afs_cb_result_handler, cc); if (ret < 0) @@ -1176,6 +1109,7 @@ int com_check(struct command_context *cc) } return 1; } +EXPORT_SERVER_CMD_HANDLER(check); /** * The afs event dispatcher. diff --git a/afs.cmd b/afs.cmd deleted file mode 100644 index 76b5f4dc..00000000 --- a/afs.cmd +++ /dev/null @@ -1,304 +0,0 @@ -BN: afs -SF: afs.c aft.c attribute.c -SN: list of afs commands -TM: mood lyr img pl ---- -N: add -P: AFS_READ | AFS_WRITE -D: Add or update audio files. -U: add [-a] [-l] [-f] [-v] path... -H: Each path must be absolute and refer to either an audio file, or a -H: directory. In case of a directory, all audio files in that directory -H: are added recursively. Only absolute paths are accepted. -H: -H: Options: -H: -H: -a Add all files. The default is to add only files ending in a -H: known suffix for a supported audio format. -H: -H: -l Add files lazily. If the path already exists in the -H: database, skip this file. This operation is really cheap. Useful -H: to update large directories after some files have been added or -H: deleted. -H: -H: -f Force adding/updating. Recompute the audio format handler data -H: even if a file with the same path and the same hash value exists. -H: -H: -v Verbose mode. Print what is being done. ---- -N: init -P: AFS_READ | AFS_WRITE -D: Initialize the osl tables for the audio file selector. -U: init [table_name ...] -H: When invoked without arguments, this command creates all tables. Otherwise -H: only the tables given by table_name... are created. ---- -N: ls -P: AFS_READ -D: List audio files. -U: ls [-l=mode] [-p] [-a] [-r] [-d] [-s=order] [pattern...] -H: Print a list of all audio files matching pattern. -H: -H: Options: -H: -H: -l=mode Change listing mode. Defaults to short listing if not given. -H: -H: Available modes: -H: s: short listing mode -H: l: long listing mode (equivalent to -l) -H: v: verbose listing mode -H: p: parser-friendly mode -H: m: mbox listing mode -H: c: chunk-table listing mode -H: -H: -F List full paths. If this option is not specified, only the basename -H: of each file is printed. -H: -p Synonym for -F. Deprecated. -H: -H: -b Print only the basename of each matching file. This is the default, so -H: the option is currently a no-op. It is recommended to specify this option, -H: though, as the default might change in a future release. -H: -H: -a List only files that are admissible with respect to the current mood or -H: playlist. -H: -H: -r Reverse sort order. -H: -H: -d Print dates as seconds after the epoch. -H: -H: -s=order -H: Change sort order. Defaults to alphabetical path sort if not given. -H: -H: Possible values for order: -H: p: by path -H: l: by last played time -H: s: by score (implies -a) -H: n: by num played count -H: f: by frequency -H: c: by number of channels -H: i: by image id -H: y: by lyrics id -H: b: by bit rate -H: d: by duration -H: a: by audio format ---- -N: lsatt -P: AFS_READ -D: List attributes. -U: lsatt [-i] [-l] [-r] [pattern] -H: Print the list of all defined attributes which match the given -H: pattern. If no pattern is given, the full list is printed. -H: -H: Options: -H: -H: -i Sort attributes by id. The default is to sort alphabetically by name. -H: -H: -l Print a long listing containing both identifier and attribute name. The -H: default is to print only the name. -H: -H: -r Reverse sort order. ---- -N: setatt -P: AFS_READ | AFS_WRITE -D: Set attribute(s) for all files matching a pattern. -U: setatt attribute{+|-}... pattern -H: Set ('+') or unset ('-') the given attributes for all audio files matching -H: pattern. Example: -H: -H: setatt rock+ punk+ pop- '*foo.mp3' -H: -H: sets the 'rock' and the 'punk' attribute and unsets the 'pop' -H: attribute of all files ending with 'foo.mp3'. ---- -N: addatt -P: AFS_READ | AFS_WRITE -D: Add new attribute(s). -U: addatt attribute1... -H: This adds new attributes to the attribute table. At most 64 -H: attributes may be defined. ---- -N: mvatt -P: AFS_READ | AFS_WRITE -D: Rename an attribute. -U: mvatt old new -H: Rename attribute old to new. ---- -N: check -P: AFS_READ -D: Run integrity checks against osl tables. -U: check [-a] [-A] [-m] [-p] -H: Check the audio file table, the attribute table, the mood definitions -H: and all defined playlists. Report any inconsistencies. -H: -H: Options: -H: -H: -a Run audio file table checks. Checks for entries in the audio file -H: table which are not present in the file system. Moreover, it checks -H: whether the lyrics id and all entries in the audio file table are -H: valid. -H: -H: -A Check the attribute table against the afs attribute bitmask of -H: each audio file in the audio file table. Reports audio files -H: whose attribute bitmask is invalid, i.e., has a bit set which -H: does not correspond to any attribute of the attribute table. -H: -H: -m Run syntax checks on all defined moods in the mood table. -H: -H: -p Check all playlists for lines that correspond to files not contained -H: in the audio file table. -H: -H: If called without arguments, all checks are run. ---- -N: rmatt -P: AFS_READ | AFS_WRITE -D: Remove attribute(s). -U: rmatt pattern... -H: Remove all attributes matching any given pattern. All information -H: about this attribute in the audio file table is lost. ---- -N: rm -P: AFS_READ | AFS_WRITE -D: Remove entries from the audio file table. -U: rm [-v] [-f] [-p] pattern... -H: Delete all entries in the audio file table that match any given pattern. Note -H: that this affects the table entries only; the command won't touch your audio -H: files on disk. -H: -H: Options: -H: -H: -v Verbose mode. Explain what is being done. -H: -H: -f Force mode. Ignore nonexistent files. Don't complain if nothing -H: was removed. -H: -H: -p Pathname match. Match a slash in the path only with a slash -H: in pattern and not by an asterisk (*) or a question mark -H: (?) metacharacter, nor by a bracket expression ([]) containing -H: a slash (see fnmatch(3)). ---- -N: touch -P: AFS_READ | AFS_WRITE -D: Manipulate the afs entry of audio files. -U: touch [-n=numplayed] [-l=lastplayed] [-y=lyrics_id] [-i=image_id] [-a=amp] [-v] [-p] pattern -H: If no option is given, the lastplayed field is set to the current time -H: and the value of the numplayed field is increased by one. Otherwise, -H: only the given options are taken into account. -H: -H: Options: -H: -H: -n Set the numplayed count, i.e. the number of times this audio -H: file was selected for streaming so far. -H: -H: -l Set the lastplayed time, i.e. the last time this audio file was -H: selected for streaming. The argument must be a number of seconds -H: since the epoch. Example: -H: -H: touch -l=$(date +%s) file -H: -H: sets the lastplayed time of 'file' to the current time. -H: -H: -y Set the lyrics ID which specifies the lyrics data file associated -H: with the audio file. -H: -H: -i Like -y, but sets the image ID. -H: -H: -a Set the amplification value (0-255). This determines a scaling -H: factor by which the amplitude should be multiplied in order to -H: normalize the volume of the audio file. A value of zero means -H: no amplification, 64 means the amplitude should be multiplied -H: by a factor of two, 128 by three and so on. -H: -H: This value is used by the amp filter. -H: -H: -v Verbose mode. Explain what is being done. -H: -H: -p Pathname match. Match a slash in the path only with a slash -H: in pattern and not by an asterisk (*) or a question mark -H: (?) metacharacter, nor by a bracket expression ([]) containing -H: a slash (see fnmatch(3)). ---- -N: cpsi -P: AFS_READ | AFS_WRITE -D: Copy audio file selector info. -U: cpsi [-a] [-y] [-i] [-l] [-n] [-v] source pattern... -H: If no option, or only the -v option is given, all fields of the -H: audio file selector info are copied to all files matching pattern. -H: Otherwise, only the given options are taken into account. -H: -H: Options: -H: -H: -a Copy attributes. -H: -H: -y Copy the lyrics id. -H: -H: -i Copy the image id. -H: -H: -l Copy the lastplayed time. -H: -H: -n Copy the numplayed count. -H: -H: -v Verbose mode. ---- -N: select -P: AFS_READ | AFS_WRITE -D: Activate a mood or a playlist. -U: select specifier/name -H: The specifier is either 'm' or 'p' to indicate whether a playlist or -H: a mood should be activated. Example: -H: -H: select m/foo -H: -H: loads the mood named 'foo'. ---- -T: add -N: add@member@ -O: int com_add@member@(struct command_context *cc); -P: AFS_READ | AFS_WRITE -D: Add stdin as a blob to the @member@ table. -U: add@member@ @member@_name -H: Read from stdin and ask the audio file selector to create a blob in the -H: corresponding osl table. If the named blob already exists, it gets replaced -H: with the new data. ---- -T: cat -N: cat@member@ -O: int com_cat@member@(struct command_context *cc); -P: AFS_READ -D: Dump the contents of a blob of type @member@ to stdout. -U: cat@member@ @member@_name -H: Retrieve the named blob and write it to stdout. ---- -T: ls -N: ls@member@ -O: int com_ls@member@(struct command_context *cc); -P: AFS_READ -D: List blobs of type @member@ which match a pattern. -U: ls@member@ [-i] [-l] [-r] [pattern] -H: Print the list of all blobs which match the given pattern. If no -H: pattern is given, the full list is printed. -H: -H: Options: -H: -H: -i Sort by identifier. The default is to sort alphabetically by name. -H: -H: -l Print identifier and name. The default is to print only the name. -H: -H: -r Reverse sort order. ---- -T: rm -N: rm@member@ -O: int com_rm@member@(struct command_context *cc); -P: AFS_READ | AFS_WRITE -D: Remove blob(s) of type @member@ from the @member@ table. -U: rm@member@ pattern... -H: Remove all blobs whose name matches any of the given patterns. ---- -T: mv -N: mv@member@ -O: int com_mv@member@(struct command_context *cc); -P: AFS_READ | AFS_WRITE -D: Rename a blob of type @member@. -U: mv@member@ source_@member@_name dest_@member@_name -H: Rename the blob identified by the source blob name to the destination blob -H: name. The command fails if the source does not exist, or if the destination -H: already exists. diff --git a/afs.h b/afs.h index 2f9d7e5f..879331ec 100644 --- a/afs.h +++ b/afs.h @@ -145,8 +145,10 @@ struct pattern_match_data { unsigned pm_flags; /** This value is passed verbatim to fnmatch(). */ int fnmatch_flags; - /** Null-terminated array of patterns. */ - struct osl_object patterns; + /** Obtained by deserializing the query buffer in the callback. */ + struct lls_parse_result *lpr; + /** Do not try to match the first inputs of lpr */ + unsigned input_skip; /** Data pointer passed to the action function. */ void *data; /** Gets increased by one for each match. */ @@ -163,6 +165,7 @@ struct afs_callback_arg { struct osl_object query; /** Will be written on band SBD_OUTPUT, fully buffered. */ struct para_buffer pbout; + struct lls_parse_result *lpr; }; /** @@ -221,13 +224,9 @@ __must_check int afs_event(enum afs_events event, struct para_buffer *pb, int send_callback_request(afs_callback *f, struct osl_object *query, callback_result_handler *result_handler, void *private_result_data); -int send_option_arg_callback_request(struct osl_object *options, - int argc, char * const * const argv, afs_callback *f, - callback_result_handler *result_handler, - void *private_result_data); -int send_standard_callback_request(int argc, char * const * const argv, - afs_callback *f, callback_result_handler *result_handler, - void *private_result_data); +int send_lls_callback_request(afs_callback *f, + const struct lls_command * const cmd, + struct lls_parse_result *lpr, void *private_result_data); int string_compare(const struct osl_object *obj1, const struct osl_object *obj2); int for_each_matching_row(struct pattern_match_data *pmd); diff --git a/aft.c b/aft.c index 1afc16bc..4a812448 100644 --- a/aft.c +++ b/aft.c @@ -11,7 +11,9 @@ #include #include #include +#include +#include "server_cmd.lsg.h" #include "para.h" #include "error.h" #include "crypt.h" @@ -86,18 +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 @@ 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 @@ 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 @@ static void save_afhi(struct afh_info *afhi, char *buf) write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels); write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total); write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len); - write_u32(buf + AFHI_UNUSED2_OFFSET, 0); + write_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET, afhi->max_chunk_size); write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec); write_u32(buf + CHUNK_TV_TV_USEC_OFFSET, afhi->chunk_tv.tv_usec); p = buf + AFHI_INFO_STRING_OFFSET; @@ -398,6 +383,7 @@ static void load_afhi(const char *buf, struct afh_info *afhi) afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET); afhi->chunks_total = read_u32(buf + CHUNKS_TOTAL_OFFSET); afhi->header_len = read_u32(buf + HEADER_LEN_OFFSET); + afhi->max_chunk_size = read_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET); afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET); afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC_OFFSET); afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET; @@ -408,42 +394,37 @@ static void load_afhi(const char *buf, struct afh_info *afhi) afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1; } +/* Only used for saving the chunk table, but not for loading. */ static unsigned sizeof_chunk_table(struct afh_info *afhi) { - if (!afhi) + if (!afhi || !afhi->chunk_table) return 0; return 4 * (afhi->chunks_total + 1); } -static uint32_t save_chunk_table(struct afh_info *afhi, char *buf) +static void save_chunk_table(struct afh_info *afhi, char *buf) { - int i; - uint32_t max = 0, old = 0; - - 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); } /** @@ -638,7 +619,13 @@ static int save_afd(struct audio_file_data *afd) goto err; buf = shm_afd; buf += sizeof(*afd); - afd->max_chunk_size = save_chunk_table(&afd->afhi, buf); + save_chunk_table(&afd->afhi, buf); + if (afd->afhi.max_chunk_size == 0) { /* v0.5.x on-disk afhi */ + set_max_chunk_size(&afd->afhi); + PARA_NOTICE_LOG("max chunk size unset, re-add required\n"); + } else + PARA_INFO_LOG("using max chunk size from afhi\n"); + afd->max_chunk_size = afd->afhi.max_chunk_size; *(struct audio_file_data *)shm_afd = *afd; shm_detach(shm_afd); return shmid; @@ -663,14 +650,22 @@ int load_afd(int shmid, struct audio_file_data *afd) { void *shm_afd; int ret; + struct osl_object obj; ret = shm_attach(shmid, ATTACH_RO, &shm_afd); if (ret < 0) return ret; + ret = shm_size(shmid, &obj.size); + if (ret < 0) + goto detach; *afd = *(struct audio_file_data *)shm_afd; - load_chunk_table(&afd->afhi, shm_afd + sizeof(*afd)); + obj.data = shm_afd + sizeof(*afd); + obj.size -= sizeof(*afd); + load_chunk_table(&afd->afhi, &obj); + ret = 1; +detach: shm_detach(shm_afd); - return 1; + return ret; } static int get_local_time(uint64_t *seconds, char *buf, size_t size, @@ -783,11 +778,11 @@ static void write_image_items(struct para_buffer *b, struct afs_info *afsi) } static void write_filename_items(struct para_buffer *b, const char *path, - unsigned flags) + bool basename) { char *val; - if (!(flags & LS_FLAG_FULL_PATH)) { + if (basename) { WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path); return; } @@ -820,7 +815,11 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) (long unsigned) d->afhi.chunk_tv.tv_usec ); buf = chunk_table_obj.data; - for (i = 0; i <= d->afhi.chunks_total; i++) + for ( + i = 0; + i <= d->afhi.chunks_total && 4 * i + 3 < chunk_table_obj.size; + i++ + ) para_printf(b, "%u ", (unsigned) read_u32(buf + 4 * i)); para_printf(b, "\n"); ret = 1; @@ -828,17 +827,12 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) return ret; } -static void write_score(struct para_buffer *b, struct ls_data *d, - struct ls_options *opts) -{ - if (!(opts->flags & LS_FLAG_ADMISSIBLE_ONLY)) /* no score*/ - return; - WRITE_STATUS_ITEM(b, SI_SCORE, "%li\n", d->score); -} - static int print_list_item(struct ls_data *d, struct ls_options *opts, struct para_buffer *b, time_t current_time) { + const struct lls_opt_result *r_a = SERVER_CMD_OPT_RESULT(LS, ADMISSIBLE, opts->lpr); + const struct lls_opt_result *r_b = SERVER_CMD_OPT_RESULT(LS, BASENAME, opts->lpr); + const struct lls_opt_result *r_d = SERVER_CMD_OPT_RESULT(LS, UNIX_DATE, opts->lpr); int ret; char att_buf[65]; char last_played_time[30]; @@ -857,7 +851,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, goto out; } get_attribute_bitmap(&afsi->attributes, att_buf); - if (opts->flags & LS_FLAG_UNIXDATE) + if (lls_opt_given(r_d)) sprintf(last_played_time, "%llu", (long long unsigned)afsi->last_played); else { @@ -869,10 +863,9 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, 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 */ @@ -912,8 +905,9 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, last_played_time, bn? bn : "?"); } - write_filename_items(b, d->path, opts->flags); - write_score(b, d, opts); + write_filename_items(b, d->path, lls_opt_given(r_b)); + if (lls_opt_given(r_a)) + WRITE_STATUS_ITEM(b, SI_SCORE, "%li\n", d->score); ret = write_attribute_items(b, att_buf, afsi); if (ret < 0) goto out; @@ -935,6 +929,8 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, 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); @@ -980,21 +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(¤t_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; @@ -1007,7 +1005,10 @@ static int make_status_items(void) 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; } /** @@ -1054,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; @@ -1072,7 +1080,7 @@ again: 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; /* @@ -1085,7 +1093,8 @@ again: 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); @@ -1161,8 +1170,16 @@ static int ls_path_compare(const void *a, const void *b) return strcmp(d1->path, d2->path); } +static inline bool admissible_only(struct ls_options *opts) +{ + return SERVER_CMD_OPT_GIVEN(LS, ADMISSIBLE, opts->lpr) + || opts->sorting == LS_SORT_BY_SCORE; +} + static int sort_matching_paths(struct ls_options *options) { + const struct lls_opt_result *r_b = SERVER_CMD_OPT_RESULT(LS, BASENAME, + options->lpr); size_t nmemb = options->num_matching_paths; size_t size = sizeof(*options->data_ptr); int (*compar)(const void *, const void *); @@ -1173,13 +1190,13 @@ static int sort_matching_paths(struct ls_options *options) options->data_ptr[i] = options->data + i; /* In these cases the array is already sorted */ - if (options->sorting == LS_SORT_BY_PATH - && !(options->flags & LS_FLAG_ADMISSIBLE_ONLY) - && (options->flags & LS_FLAG_FULL_PATH)) - return 1; - if (options->sorting == LS_SORT_BY_SCORE && - options->flags & LS_FLAG_ADMISSIBLE_ONLY) - return 1; + if (admissible_only(options)) { + if (options->sorting == LS_SORT_BY_SCORE) + return 1; + } else { + if (options->sorting == LS_SORT_BY_PATH && !lls_opt_given(r_b)) + return 1; + } switch (options->sorting) { case LS_SORT_BY_PATH: @@ -1217,15 +1234,16 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) { int ret, i; struct ls_options *options = ls_opts; + bool basename_given = SERVER_CMD_OPT_GIVEN(LS, BASENAME, options->lpr); struct ls_data *d; struct ls_widths *w; unsigned short num_digits; - unsigned tmp; + unsigned tmp, num_inputs; struct osl_row *aft_row; long score; char *path; - if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) { + if (admissible_only(options)) { ret = get_score_and_aft_row(row, &score, &aft_row); if (ret < 0) return ret; @@ -1236,21 +1254,22 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) ret = get_audio_file_path_of_row(aft_row, &path); if (ret < 0) return ret; - if (!(options->flags & LS_FLAG_FULL_PATH)) { + if (basename_given) { char *p = strrchr(path, '/'); if (p) path = p + 1; } - if (options->num_patterns) { - for (i = 0; i < options->num_patterns; i++) { - ret = fnmatch(options->patterns[i], path, 0); + num_inputs = lls_num_inputs(options->lpr); + if (num_inputs > 0) { + for (i = 0; i < num_inputs; i++) { + ret = fnmatch(lls_input(i, options->lpr), path, 0); if (!ret) break; if (ret == FNM_NOMATCH) continue; return -E_FNMATCH; } - if (i >= options->num_patterns) /* no match */ + if (i >= num_inputs) /* no match */ return 1; } tmp = options->num_matching_paths++; @@ -1289,7 +1308,7 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) w->amp_width = PARA_MAX(w->amp_width, num_digits); num_digits = strlen(audio_format_name(d->afsi.audio_format_id)); w->audio_format_width = PARA_MAX(w->audio_format_width, num_digits); - if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) { + if (admissible_only(options)) { GET_NUM_DIGITS(score, &num_digits); num_digits++; /* add one for the sign (space or "-") */ w->score_width = PARA_MAX(w->score_width, num_digits); @@ -1302,21 +1321,19 @@ err: static int com_ls_callback(struct afs_callback_arg *aca) { + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); struct ls_options *opts = aca->query.data; - char *p, *pattern_start = (char *)aca->query.data + sizeof(*opts); int i = 0, ret; time_t current_time; + const struct lls_opt_result *r_r; + + ret = lls_deserialize_parse_result( + (char *)aca->query.data + sizeof(*opts), cmd, &opts->lpr); + assert(ret >= 0); + r_r = SERVER_CMD_OPT_RESULT(LS, REVERSE, opts->lpr); aca->pbout.flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0; - if (opts->num_patterns) { - opts->patterns = para_malloc(opts->num_patterns * sizeof(char *)); - for (i = 0, p = pattern_start; i < opts->num_patterns; i++) { - opts->patterns[i] = p; - p += strlen(p) + 1; - } - } else - opts->patterns = NULL; - if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) + if (admissible_only(opts)) ret = admissible_file_loop(opts, prepare_ls_row); else ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, @@ -1324,14 +1341,14 @@ static int com_ls_callback(struct afs_callback_arg *aca) if (ret < 0) goto out; if (opts->num_matching_paths == 0) { - ret = opts->num_patterns > 0? -E_NO_MATCH : 0; + ret = lls_num_inputs(opts->lpr) > 0? -E_NO_MATCH : 0; goto out; } ret = sort_matching_paths(opts); if (ret < 0) goto out; time(¤t_time); - if (opts->flags & LS_FLAG_REVERSE) + if (lls_opt_given(r_r)) for (i = opts->num_matching_paths - 1; i >= 0; i--) { ret = print_list_item(opts->data_ptr[i], opts, &aca->pbout, current_time); @@ -1346,143 +1363,90 @@ static int com_ls_callback(struct afs_callback_arg *aca) goto out; } out: + lls_free_parse_result(opts->lpr, cmd); free(opts->data); free(opts->data_ptr); - free(opts->patterns); return ret; } -/* - * TODO: flags -h (sort by hash) - */ -int com_ls(struct command_context *cc) +/* 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. @@ -1522,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. */ @@ -1540,31 +1504,32 @@ enum com_add_buffer_offsets { * handler info won't be stored in the buffer. */ static void save_add_callback_buffer(unsigned char *hash, const char *path, - struct afh_info *afhi, uint32_t flags, + struct afh_info *afhi, const char *slpr, size_t slpr_size, uint8_t audio_format_num, struct osl_object *obj) { size_t path_len = strlen(path) + 1; size_t afhi_size = sizeof_afhi_buf(afhi); size_t size = CAB_PATH_OFFSET + path_len + afhi_size - + sizeof_chunk_table(afhi); + + sizeof_chunk_table(afhi) + slpr_size; char *buf = para_malloc(size); uint32_t pos; + assert(size <= ~(uint32_t)0); + write_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET, audio_format_num); + memcpy(buf + CAB_HASH_OFFSET, hash, HASH_SIZE); + strcpy(buf + CAB_PATH_OFFSET, path); pos = CAB_PATH_OFFSET + path_len; write_u32(buf + CAB_AFHI_OFFSET_POS, pos); save_afhi(afhi, buf + pos); pos += afhi_size; - write_u32(buf + CAB_CHUNKS_OFFSET_POS, pos); - if (afhi) + if (afhi) { save_chunk_table(afhi, buf + pos); - - write_u32(buf + CAB_FLAGS_OFFSET, flags); - write_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET, audio_format_num); - - memcpy(buf + CAB_HASH_OFFSET, hash, HASH_SIZE); - strcpy(buf + CAB_PATH_OFFSET, path); - + pos += sizeof_chunk_table(afhi); + } + write_u32(buf + CAB_LPR_OFFSET, pos); + memcpy(buf + pos, slpr, slpr_size); + assert(pos + slpr_size == size); obj->data = buf; obj->size = size; } @@ -1620,18 +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; @@ -1642,9 +1595,16 @@ static int com_add_callback(struct afs_callback_arg *aca) char asc[2 * HASH_SIZE + 1]; int ret; char afsi_buf[AFSI_SIZE]; - uint32_t flags = read_u32(buf + CAB_FLAGS_OFFSET); + char *slpr = buf + read_u32(buf + CAB_LPR_OFFSET); struct afs_info default_afsi = {.last_played = 0}; uint16_t afhi_offset, chunks_offset; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADD); + const struct lls_opt_result *r_f, *r_v; + + ret = lls_deserialize_parse_result(slpr, cmd, &aca->lpr); + assert(ret >= 0); + r_f = SERVER_CMD_OPT_RESULT(ADD, FORCE, aca->lpr); + r_v = SERVER_CMD_OPT_RESULT(ADD, VERBOSE, aca->lpr); hash = (unsigned char *)buf + CAB_HASH_OFFSET; hash_to_asc(hash, asc); @@ -1662,8 +1622,8 @@ static int com_add_callback(struct afs_callback_arg *aca) ret = find_path_brother(path, &pb); if (ret < 0) goto out; - if (hs && pb && hs == pb && !(flags & ADD_FLAG_FORCE)) { - if (flags & ADD_FLAG_VERBOSE) + if (hs && pb && hs == pb && !lls_opt_given(r_f)) { + if (lls_opt_given(r_v)) para_printf(&aca->pbout, "ignoring duplicate\n"); ret = 1; goto out; @@ -1671,7 +1631,7 @@ static int com_add_callback(struct afs_callback_arg *aca) if (hs && hs != pb) { struct osl_object obj; if (pb) { /* hs trumps pb, remove pb */ - if (flags & ADD_FLAG_VERBOSE) + if (lls_opt_given(r_v)) para_printf(&aca->pbout, "removing %s\n", path); ret = afs_event(AUDIO_FILE_REMOVE, &aca->pbout, pb); if (ret < 0) @@ -1682,7 +1642,7 @@ static int com_add_callback(struct afs_callback_arg *aca) pb = NULL; } /* file rename, update hs' path */ - if (flags & ADD_FLAG_VERBOSE) { + if (lls_opt_given(r_v)) { ret = osl(osl_get_object(audio_file_table, hs, AFTCOL_PATH, &obj)); if (ret < 0) @@ -1697,7 +1657,7 @@ static int com_add_callback(struct afs_callback_arg *aca) ret = afs_event(AUDIO_FILE_RENAME, &aca->pbout, hs); if (ret < 0) goto out; - if (!(flags & ADD_FLAG_FORCE)) + if (!lls_opt_given(r_f)) goto out; } /* no hs or force mode, child must have sent afhi */ @@ -1718,35 +1678,34 @@ static int com_add_callback(struct afs_callback_arg *aca) if (ret < 0) goto out; hash_to_asc(old_hash, old_asc); - if (flags & ADD_FLAG_VERBOSE) + if (lls_opt_given(r_v)) para_printf(&aca->pbout, "file change: %s -> %s\n", old_asc, asc); - ret = osl_update_object(audio_file_table, pb, AFTCOL_HASH, - &objs[AFTCOL_HASH]); + ret = osl(osl_update_object(audio_file_table, pb, AFTCOL_HASH, + &objs[AFTCOL_HASH])); if (ret < 0) goto out; } if (hs || pb) { /* (hs != NULL and pb != NULL) implies hs == pb */ struct osl_row *row = pb? pb : hs; /* update afhi and chunk_table */ - if (flags & ADD_FLAG_VERBOSE) + if (lls_opt_given(r_v)) para_printf(&aca->pbout, "updating afhi and chunk table\n"); ret = osl(osl_update_object(audio_file_table, row, AFTCOL_AFHI, &objs[AFTCOL_AFHI])); if (ret < 0) goto out; + /* truncate the file to size zero if there is no chunk table */ ret = osl(osl_update_object(audio_file_table, row, AFTCOL_CHUNKS, &objs[AFTCOL_CHUNKS])); if (ret < 0) goto out; ret = afs_event(AFHI_CHANGE, &aca->pbout, row); - if (ret < 0) - goto out; goto out; } /* new entry, use default afsi */ - if (flags & ADD_FLAG_VERBOSE) + if (lls_opt_given(r_v)) para_printf(&aca->pbout, "new file\n"); default_afsi.last_played = time(NULL) - 365 * 24 * 60 * 60; default_afsi.audio_format_id = read_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET); @@ -1761,15 +1720,20 @@ static int com_add_callback(struct afs_callback_arg *aca) out: if (ret < 0) para_printf(&aca->pbout, "could not add %s\n", path); + lls_free_parse_result(aca->lpr, cmd); return ret; } -/** Used by com_add(). */ +/* Used by com_add(). */ struct private_add_data { - /** The pointer passed to the original command handler. */ + /* The pointer passed to the original command handler. */ struct command_context *cc; - /** The given add flags. */ - uint32_t flags; + /* Contains the flags given at the command line. */ + struct lls_parse_result *lpr; + /* Serialized lopsub parse result. */ + char *slpr; + /* Number of bytes. */ + size_t slpr_size; }; static int path_brother_callback(struct afs_callback_arg *aca) @@ -1814,9 +1778,13 @@ static int add_one_audio_file(const char *path, void *private_data) struct osl_row *pb = NULL, *hs = NULL; /* path brother/hash sister */ struct osl_object map, obj = {.data = NULL}, query; unsigned char hash[HASH_SIZE]; + bool a_given = SERVER_CMD_OPT_GIVEN(ADD, ALL, pad->lpr); + bool f_given = SERVER_CMD_OPT_GIVEN(ADD, FORCE, pad->lpr); + bool l_given = SERVER_CMD_OPT_GIVEN(ADD, LAZY, pad->lpr); + bool v_given = SERVER_CMD_OPT_GIVEN(ADD, VERBOSE, pad->lpr); ret = guess_audio_format(path); - if (ret < 0 && !(pad->flags & ADD_FLAG_ALL)) { + if (ret < 0 && !a_given) { ret = 0; goto out_free; } @@ -1827,8 +1795,8 @@ static int add_one_audio_file(const char *path, void *private_data) if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) goto out_free; ret = 1; - if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */ - if (pad->flags & ADD_FLAG_VERBOSE) + if (pb && l_given) { /* lazy is really cheap */ + if (v_given) send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT, "lazy-ignore: %s\n", path); goto out_free; @@ -1848,8 +1816,8 @@ static int add_one_audio_file(const char *path, void *private_data) goto out_unmap; /* Return success if we already know this file. */ ret = 1; - if (pb && hs && hs == pb && !(pad->flags & ADD_FLAG_FORCE)) { - if (pad->flags & ADD_FLAG_VERBOSE) + if (pb && hs && hs == pb && !f_given) { + if (v_given) send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT, "%s exists, not forcing update\n", path); goto out_unmap; @@ -1858,7 +1826,7 @@ static int add_one_audio_file(const char *path, void *private_data) * We won't recalculate the audio format info and the chunk table if * there is a hash sister and FORCE was not given. */ - if (!hs || (pad->flags & ADD_FLAG_FORCE)) { + if (!hs || f_given) { ret = compute_afhi(path, map.data, map.size, fd, &afhi); if (ret < 0) goto out_unmap; @@ -1867,13 +1835,14 @@ static int add_one_audio_file(const char *path, void *private_data) } munmap(map.data, map.size); close(fd); - if (pad->flags & ADD_FLAG_VERBOSE) { + if (v_given) { send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT, "adding %s\n", path); if (send_ret < 0) goto out_free; } - save_add_callback_buffer(hash, path, afhi_ptr, pad->flags, format_num, &obj); + save_add_callback_buffer(hash, path, afhi_ptr, pad->slpr, + pad->slpr_size, format_num, &obj); /* Ask afs to consider this entry for adding. */ ret = send_callback_request(com_add_callback, &obj, afs_cb_result_handler, pad->cc); @@ -1892,46 +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 */ @@ -1947,8 +1900,12 @@ int com_add(struct command_context *cc) } free(path); } - return 1; + ret = 1; +out: + free(pad.slpr); + return ret; } +EXPORT_SERVER_CMD_HANDLER(add); /** * Flags used by the touch command. @@ -1962,33 +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); @@ -2003,23 +1953,23 @@ static int touch_audio_file(__a_unused struct osl_table *table, if (no_options) { new_afsi.num_played++; new_afsi.last_played = time(NULL); - if (cto->flags & TOUCH_FLAG_VERBOSE) + if (v_given) para_printf(&aca->pbout, "%s: num_played = %u, " "last_played = now()\n", name, new_afsi.num_played); } else { - if (cto->flags & TOUCH_FLAG_VERBOSE) + if (lls_opt_given(r_l)) + new_afsi.last_played = lls_uint64_val(0, r_l); + if (lls_opt_given(r_n)) + new_afsi.num_played = lls_uint32_val(0, r_n); + if (lls_opt_given(r_i)) + new_afsi.image_id = lls_uint32_val(0, r_i); + if (lls_opt_given(r_y)) + new_afsi.lyrics_id = lls_uint32_val(0, r_y); + if (lls_opt_given(r_a)) + new_afsi.amp = lls_uint32_val(0, r_a); + if (v_given) para_printf(&aca->pbout, "touching %s\n", name); - if (cto->lyrics_id >= 0) - new_afsi.lyrics_id = cto->lyrics_id; - if (cto->image_id >= 0) - new_afsi.image_id = cto->image_id; - if (cto->num_played >= 0) - new_afsi.num_played = cto->num_played; - if (cto->last_played >= 0) - new_afsi.last_played = cto->last_played; - if (cto->amp >= 0) - new_afsi.amp = cto->amp; } save_afsi(&new_afsi, &obj); /* in-place update */ aced.aft_row = row; @@ -2029,134 +1979,73 @@ static int touch_audio_file(__a_unused struct osl_table *table, static int com_touch_callback(struct afs_callback_arg *aca) { + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(TOUCH); + bool p_given; + const struct lls_opt_result *r_i, *r_y; int ret; - struct com_touch_options *cto = aca->query.data; struct pattern_match_data pmd = { .table = audio_file_table, .loop_col_num = AFTCOL_HASH, .match_col_num = AFTCOL_PATH, - .patterns = { - .data = (char *)aca->query.data - + sizeof(struct com_touch_options), - .size = aca->query.size - - sizeof(struct com_touch_options) - }, .data = aca, .action = touch_audio_file }; - if (cto->image_id >= 0) { - ret = img_get_name_by_id(cto->image_id, NULL); + + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; + + r_i = SERVER_CMD_OPT_RESULT(TOUCH, IMAGE_ID, aca->lpr); + if (lls_opt_given(r_i)) { + uint32_t id = lls_uint32_val(0, r_i); + ret = img_get_name_by_id(id, NULL); if (ret < 0) { - para_printf(&aca->pbout, "invalid image ID: %d\n", - cto->image_id); + para_printf(&aca->pbout, "invalid image ID: %u\n", id); return ret; } } - if (cto->lyrics_id >= 0) { - ret = lyr_get_name_by_id(cto->lyrics_id, NULL); + r_y = SERVER_CMD_OPT_RESULT(TOUCH, LYRICS_ID, aca->lpr); + if (lls_opt_given(r_y)) { + uint32_t id = lls_uint32_val(0, r_y); + ret = lyr_get_name_by_id(id, NULL); if (ret < 0) { - para_printf(&aca->pbout, "invalid lyrics ID: %d\n", - cto->lyrics_id); + para_printf(&aca->pbout, "invalid lyrics ID: %u\n", id); return ret; } } - if (cto->flags & TOUCH_FLAG_FNM_PATHNAME) + p_given = SERVER_CMD_OPT_GIVEN(TOUCH, PATHNAME_MATCH, aca->lpr); + if (p_given) pmd.fnmatch_flags |= FNM_PATHNAME; ret = for_each_matching_row(&pmd); if (ret >= 0 && pmd.num_matches == 0) ret = -E_NO_MATCH; + lls_free_parse_result(aca->lpr, cmd); return ret; } -int com_touch(struct command_context *cc) +static int com_touch(struct command_context *cc, struct lls_parse_result *lpr) { - struct com_touch_options cto = { - .num_played = -1, - .last_played = -1, - .lyrics_id = -1, - .image_id = -1, - .amp = -1, - }; - struct osl_object query = {.data = &cto, .size = sizeof(cto)}; - int i, ret; - + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(TOUCH); + int ret; + char *errctx; - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strncmp(arg, "-n=", 3)) { - ret = para_atoi32(arg + 3, &cto.num_played); - if (ret < 0) - return ret; - continue; - } - if (!strncmp(arg, "-l=", 3)) { - ret = para_atoi64(arg + 3, &cto.last_played); - if (ret < 0) - return ret; - continue; - } - if (!strncmp(arg, "-y=", 3)) { - ret = para_atoi32(arg + 3, &cto.lyrics_id); - if (ret < 0) - return ret; - continue; - } - if (!strncmp(arg, "-i=", 3)) { - ret = para_atoi32(arg + 3, &cto.image_id); - if (ret < 0) - return ret; - continue; - } - if (!strncmp(arg, "-a=", 3)) { - int32_t val; - ret = para_atoi32(arg + 3, &val); - if (ret < 0) - return ret; - if (val < 0 || val > 255) - return -ERRNO_TO_PARA_ERROR(EINVAL); - cto.amp = val; - continue; - } - if (!strcmp(arg, "-p")) { - cto.flags |= TOUCH_FLAG_FNM_PATHNAME; - continue; - } - if (!strcmp(arg, "-v")) { - cto.flags |= TOUCH_FLAG_VERBOSE; - continue; - } - break; /* non-option starting with dash */ + ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; } - if (i >= cc->argc) - return -E_AFT_SYNTAX; - return send_option_arg_callback_request(&query, cc->argc - i, - cc->argv + i, com_touch_callback, afs_cb_result_handler, cc); -} - -/** Flags for com_rm(). */ -enum rm_flags { - /** -v */ - RM_FLAG_VERBOSE = 1, - /** -f */ - RM_FLAG_FORCE = 2, - /** -p */ - RM_FLAG_FNM_PATHNAME = 4 -}; + return send_lls_callback_request(com_touch_callback, cmd, lpr, cc); +} +EXPORT_SERVER_CMD_HANDLER(touch); static int remove_audio_file(__a_unused struct osl_table *table, struct osl_row *row, const char *name, void *data) { struct afs_callback_arg *aca = data; - uint32_t flags =*(uint32_t *)aca->query.data; + bool v_given = SERVER_CMD_OPT_GIVEN(RM, VERBOSE, aca->lpr); int ret; - if (flags & RM_FLAG_VERBOSE) + if (v_given) para_printf(&aca->pbout, "removing %s\n", name); ret = afs_event(AUDIO_FILE_REMOVE, &aca->pbout, row); if (ret < 0) @@ -2169,95 +2058,63 @@ static int remove_audio_file(__a_unused struct osl_table *table, static int com_rm_callback(struct afs_callback_arg *aca) { + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RM); int ret; - uint32_t flags = *(uint32_t *)aca->query.data; struct pattern_match_data pmd = { .table = audio_file_table, .loop_col_num = AFTCOL_HASH, .match_col_num = AFTCOL_PATH, - .patterns = {.data = (char *)aca->query.data + sizeof(uint32_t), - .size = aca->query.size - sizeof(uint32_t)}, .data = aca, .action = remove_audio_file }; - if (flags & RM_FLAG_FNM_PATHNAME) + bool v_given, p_given, f_given; + + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; + v_given = SERVER_CMD_OPT_GIVEN(RM, VERBOSE, aca->lpr); + p_given = SERVER_CMD_OPT_GIVEN(RM, PATHNAME_MATCH, aca->lpr); + f_given = SERVER_CMD_OPT_GIVEN(RM, FORCE, aca->lpr); + + if (p_given) pmd.fnmatch_flags |= FNM_PATHNAME; ret = for_each_matching_row(&pmd); if (ret < 0) goto out; if (pmd.num_matches == 0) { - if (!(flags & RM_FLAG_FORCE)) + if (!f_given) ret = -E_NO_MATCH; - } else if (flags & RM_FLAG_VERBOSE) + } else if (v_given) para_printf(&aca->pbout, "removed %u file(s)\n", pmd.num_matches); out: + lls_free_parse_result(aca->lpr, cmd); return ret; } /* TODO options: -r (recursive) */ -int com_rm(struct command_context *cc) +static int com_rm(struct command_context *cc, struct lls_parse_result *lpr) { - uint32_t flags = 0; - struct osl_object query = {.data = &flags, .size = sizeof(flags)}; - int i; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RM); + char *errctx; + int ret; - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-f")) { - flags |= RM_FLAG_FORCE; - continue; - } - if (!strcmp(arg, "-p")) { - flags |= RM_FLAG_FNM_PATHNAME; - continue; - } - if (!strcmp(arg, "-v")) { - flags |= RM_FLAG_VERBOSE; - continue; - } - break; + ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; } - if (i >= cc->argc) - return -E_AFT_SYNTAX; - return send_option_arg_callback_request(&query, cc->argc - i, - cc->argv + i, com_rm_callback, afs_cb_result_handler, cc); + return send_lls_callback_request(com_rm_callback, cmd, lpr, cc); } - -/** - * Flags used by the cpsi command. - * - * \sa com_cpsi(). - */ -enum cpsi_flags { - /** Whether the lyrics id should be copied. */ - CPSI_FLAG_COPY_LYRICS_ID = 1, - /** Whether the image id should be copied. */ - CPSI_FLAG_COPY_IMAGE_ID = 2, - /** Whether the lastplayed time should be copied. */ - CPSI_FLAG_COPY_LASTPLAYED = 4, - /** Whether the numplayed count should be copied. */ - CPSI_FLAG_COPY_NUMPLAYED = 8, - /** Whether the attributes should be copied. */ - CPSI_FLAG_COPY_ATTRIBUTES = 16, - /** Activates verbose mode. */ - CPSI_FLAG_VERBOSE = 32, -}; +EXPORT_SERVER_CMD_HANDLER(rm); /** Data passed to the action handler of com_cpsi(). */ struct cpsi_action_data { - /** command line flags (see \ref cpsi_flags). */ - unsigned flags; /** Values are copied from here. */ struct afs_info source_afsi; /** What was passed to com_cpsi_callback(). */ struct afs_callback_arg *aca; + bool copy_all; }; static int copy_selector_info(__a_unused struct osl_table *table, @@ -2268,6 +2125,14 @@ static int copy_selector_info(__a_unused struct osl_table *table, int ret; struct afs_info old_afsi, target_afsi; struct afsi_change_event_data aced; + bool a_given, y_given, i_given, l_given, n_given, v_given; + + a_given = SERVER_CMD_OPT_GIVEN(CPSI, ATTRIBUTE_BITMAP, cad->aca->lpr); + y_given = SERVER_CMD_OPT_GIVEN(CPSI, LYRICS_ID, cad->aca->lpr); + i_given = SERVER_CMD_OPT_GIVEN(CPSI, IMAGE_ID, cad->aca->lpr); + l_given = SERVER_CMD_OPT_GIVEN(CPSI, LASTPLAYED, cad->aca->lpr); + n_given = SERVER_CMD_OPT_GIVEN(CPSI, NUMPLAYED, cad->aca->lpr); + v_given = SERVER_CMD_OPT_GIVEN(CPSI, VERBOSE, cad->aca->lpr); ret = get_afsi_object_of_row(row, &target_afsi_obj); if (ret < 0) @@ -2276,18 +2141,18 @@ static int copy_selector_info(__a_unused struct osl_table *table, if (ret < 0) return ret; old_afsi = target_afsi; - if (cad->flags & CPSI_FLAG_COPY_LYRICS_ID) + if (cad->copy_all || y_given) target_afsi.lyrics_id = cad->source_afsi.lyrics_id; - if (cad->flags & CPSI_FLAG_COPY_IMAGE_ID) + if (cad->copy_all || i_given) target_afsi.image_id = cad->source_afsi.image_id; - if (cad->flags & CPSI_FLAG_COPY_LASTPLAYED) + if (cad->copy_all || l_given) target_afsi.last_played = cad->source_afsi.last_played; - if (cad->flags & CPSI_FLAG_COPY_NUMPLAYED) + if (cad->copy_all || n_given) target_afsi.num_played = cad->source_afsi.num_played; - if (cad->flags & CPSI_FLAG_COPY_ATTRIBUTES) + if (cad->copy_all || a_given) target_afsi.attributes = cad->source_afsi.attributes; save_afsi(&target_afsi, &target_afsi_obj); /* in-place update */ - if (cad->flags & CPSI_FLAG_VERBOSE) + if (v_given) para_printf(&cad->aca->pbout, "copied afsi to %s\n", name); aced.aft_row = row; aced.old_afsi = &old_afsi; @@ -2296,86 +2161,60 @@ static int copy_selector_info(__a_unused struct osl_table *table, static int com_cpsi_callback(struct afs_callback_arg *aca) { - struct cpsi_action_data cad = { - .flags = *(unsigned *)aca->query.data, - .aca = aca - }; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(CPSI); + bool a_given, y_given, i_given, l_given, n_given, v_given; + struct cpsi_action_data cad = {.aca = aca}; int ret; - char *source_path = (char *)aca->query.data + sizeof(cad.flags); struct pattern_match_data pmd = { .table = audio_file_table, .loop_col_num = AFTCOL_HASH, .match_col_num = AFTCOL_PATH, - .patterns = {.data = source_path + strlen(source_path) + 1, - .size = aca->query.size - sizeof(cad.flags) - - strlen(source_path) - 1}, + .input_skip = 1, /* skip first argument (source file) */ .data = &cad, .action = copy_selector_info }; - ret = get_afsi_of_path(source_path, &cad.source_afsi); + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; + + a_given = SERVER_CMD_OPT_GIVEN(CPSI, ATTRIBUTE_BITMAP, aca->lpr); + y_given = SERVER_CMD_OPT_GIVEN(CPSI, LYRICS_ID, aca->lpr); + i_given = SERVER_CMD_OPT_GIVEN(CPSI, IMAGE_ID, aca->lpr); + l_given = SERVER_CMD_OPT_GIVEN(CPSI, LASTPLAYED, aca->lpr); + n_given = SERVER_CMD_OPT_GIVEN(CPSI, NUMPLAYED, aca->lpr); + v_given = SERVER_CMD_OPT_GIVEN(CPSI, VERBOSE, aca->lpr); + cad.copy_all = !a_given && !y_given && !i_given && !l_given && !n_given; + + ret = get_afsi_of_path(lls_input(0, aca->lpr), &cad.source_afsi); if (ret < 0) goto out; ret = for_each_matching_row(&pmd); if (ret < 0) goto out; if (pmd.num_matches > 0) { - if (cad.flags & CPSI_FLAG_VERBOSE) + if (v_given) para_printf(&aca->pbout, "updated afsi of %u file(s)\n", pmd.num_matches); } else ret = -E_NO_MATCH; out: + lls_free_parse_result(aca->lpr, cmd); return ret; } -int com_cpsi(struct command_context *cc) +static int com_cpsi(struct command_context *cc, struct lls_parse_result *lpr) { - unsigned flags = 0; - int i; - struct osl_object options = {.data = &flags, .size = sizeof(flags)}; - - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-y")) { - flags |= CPSI_FLAG_COPY_LYRICS_ID; - continue; - } - if (!strcmp(arg, "-i")) { - flags |= CPSI_FLAG_COPY_IMAGE_ID; - continue; - } - if (!strcmp(arg, "-l")) { - flags |= CPSI_FLAG_COPY_LASTPLAYED; - continue; - } - if (!strcmp(arg, "-n")) { - flags |= CPSI_FLAG_COPY_NUMPLAYED; - continue; - } - if (!strcmp(arg, "-a")) { - flags |= CPSI_FLAG_COPY_ATTRIBUTES; - continue; - } - if (!strcmp(arg, "-v")) { - flags |= CPSI_FLAG_VERBOSE; - continue; - } - break; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(CPSI); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 2, INT_MAX, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; } - if (i + 1 >= cc->argc) /* need at least source file and pattern */ - return -E_AFT_SYNTAX; - if (!(flags & ~CPSI_FLAG_VERBOSE)) /* no copy flags given */ - flags = ~(unsigned)CPSI_FLAG_VERBOSE | flags; - return send_option_arg_callback_request(&options, cc->argc - i, - cc->argv + i, com_cpsi_callback, afs_cb_result_handler, cc); + return send_lls_callback_request(com_cpsi_callback, cmd, lpr, cc); } +EXPORT_SERVER_CMD_HANDLER(cpsi); struct change_atts_data { uint64_t add_mask, del_mask; @@ -2409,9 +2248,8 @@ static int change_atts(__a_unused struct osl_table *table, static int com_setatt_callback(struct afs_callback_arg *aca) { - char *p; - int ret; - size_t len; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SETATT); + int i, ret; struct change_atts_data cad = {.aca = aca}; struct pattern_match_data pmd = { .table = audio_file_table, @@ -2421,27 +2259,36 @@ static int com_setatt_callback(struct afs_callback_arg *aca) .data = &cad, .action = change_atts }; + unsigned num_inputs; - for ( - p = aca->query.data; - p < (char *)aca->query.data + aca->query.size; - p += len + 1 - ) { - char c; + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; + + num_inputs = lls_num_inputs(aca->lpr); + for (i = 0; i < num_inputs; i++) { unsigned char bitnum; uint64_t one = 1; + const char *arg = lls_input(i, aca->lpr); + char c, *p; + size_t len = strlen(arg); - len = strlen(p); ret = -E_ATTR_SYNTAX; if (len == 0) goto out; - c = p[len - 1]; - if (c != '+' && c != '-') - break; + c = arg[len - 1]; + if (c != '+' && c != '-') { + if (cad.add_mask == 0 && cad.del_mask == 0) + goto out; /* no attribute modifier given */ + goto set_atts; + } + p = para_malloc(len); + memcpy(p, arg, len - 1); p[len - 1] = '\0'; ret = get_attribute_bitnum_by_name(p, &bitnum); + free(p); if (ret < 0) { - para_printf(&aca->pbout, "attribute not found: %s\n", p); + para_printf(&aca->pbout, "invalid argument: %s\n", arg); goto out; } if (c == '+') @@ -2449,29 +2296,32 @@ static int com_setatt_callback(struct afs_callback_arg *aca) else cad.del_mask |= (one << bitnum); } + /* no pattern given */ ret = -E_ATTR_SYNTAX; - if (!cad.add_mask && !cad.del_mask) - goto out; - pmd.patterns.data = p; - if (p >= (char *)aca->query.data + aca->query.size) - goto out; - pmd.patterns.size = (char *)aca->query.data + aca->query.size - p; + goto out; +set_atts: + pmd.input_skip = i; ret = for_each_matching_row(&pmd); - if (ret < 0) - goto out; - if (pmd.num_matches == 0) + if (ret >= 0 && pmd.num_matches == 0) ret = -E_NO_MATCH; out: + lls_free_parse_result(aca->lpr, cmd); return ret; } -int com_setatt(struct command_context *cc) +static int com_setatt(struct command_context *cc, struct lls_parse_result *lpr) { - if (cc->argc < 3) - return -E_ATTR_SYNTAX; - return send_standard_callback_request(cc->argc - 1, cc->argv + 1, - com_setatt_callback, afs_cb_result_handler, cc); + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SETATT); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 2, INT_MAX, &errctx)); + + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(com_setatt_callback, cmd, lpr, cc); } +EXPORT_SERVER_CMD_HANDLER(setatt); static int afs_stat_callback(struct afs_callback_arg *aca) { diff --git a/alsa_mix.c b/alsa_mix.c index 3adee929..6520416f 100644 --- a/alsa_mix.c +++ b/alsa_mix.c @@ -216,19 +216,13 @@ static int alsa_mix_set(struct mixer_handle *h, int val) return 1; } -/** - * The init function of the ALSA mixer. - * - * \param self The structure to initialize. - * - * \sa struct \ref mixer, \ref oss_mix_init(). - */ -void alsa_mix_init(struct mixer *self) -{ - self->open = alsa_mix_open; - self->get_channels = alsa_mix_get_channels; - self->set_channel = alsa_mix_set_channel; - self->close = alsa_mix_close; - self->get = alsa_mix_get; - self->set = alsa_mix_set; -} +/** The mixer operations for the ALSA mixer. */ +const struct mixer alsa_mixer = { + .name = "alsa", + .open = alsa_mix_open, + .get_channels = alsa_mix_get_channels, + .set_channel = alsa_mix_set_channel, + .close = alsa_mix_close, + .get = alsa_mix_get, + .set = alsa_mix_set +}; diff --git a/alsa_write.c b/alsa_write.c index fd3b404c..3935c8c6 100644 --- a/alsa_write.c +++ b/alsa_write.c @@ -15,17 +15,16 @@ #include #include #include +#include +#include "write_cmd.lsg.h" #include "para.h" #include "fd.h" #include "string.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "write.h" -#include "write_common.h" -#include "alsa_write.cmdline.h" #include "error.h" /** Data specific to the alsa writer. */ @@ -71,22 +70,22 @@ static snd_pcm_format_t get_alsa_pcm_format(enum sample_format sf) } /* Install PCM software and hardware configuration. */ -static int alsa_init(struct private_alsa_write_data *pad, - struct alsa_write_args_info *conf) +static int alsa_init(struct writer_node *wn) { + struct private_alsa_write_data *pad = wn->private_data; snd_pcm_hw_params_t *hwparams = NULL; snd_pcm_sw_params_t *swparams = NULL; snd_pcm_uframes_t start_threshold, stop_threshold; snd_pcm_uframes_t buffer_size, period_size; snd_output_t *output_log; int ret; - const char *msg; + const char *msg, *dev = WRITE_CMD_OPT_STRING_VAL(ALSA, DEVICE, wn->lpr); unsigned period_time; - PARA_INFO_LOG("opening %s\n", conf->device_arg); + PARA_INFO_LOG("opening %s\n", dev); msg = "unable to open pcm"; - ret = snd_pcm_open(&pad->handle, conf->device_arg, - SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + ret = snd_pcm_open(&pad->handle, dev, SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK); if (ret < 0) goto fail; ret = snd_pcm_hw_params_malloc(&hwparams); @@ -116,7 +115,8 @@ static int alsa_init(struct private_alsa_write_data *pad, if (ret < 0) goto fail; /* alsa wants microseconds */ - pad->buffer_time = conf->buffer_time_arg * 1000; + pad->buffer_time = 1000U * WRITE_CMD_OPT_UINT32_VAL(ALSA, BUFFER_TIME, + wn->lpr); msg = "could not set buffer time"; ret = snd_pcm_hw_params_set_buffer_time_near(pad->handle, hwparams, &pad->buffer_time, NULL); @@ -295,7 +295,7 @@ again: if (bytes == 0) /* no data available */ return 0; - pad = para_calloc(sizeof(*pad)); + pad = wn->private_data = para_calloc(sizeof(*pad)); get_btr_sample_rate(btrn, &val); pad->sample_rate = val; get_btr_channels(btrn, &val); @@ -305,12 +305,12 @@ again: PARA_INFO_LOG("%u channel(s), %uHz\n", pad->channels, pad->sample_rate); - ret = alsa_init(pad, wn->conf); + ret = alsa_init(wn); if (ret < 0) { - free(pad); + free(wn->private_data); + wn->private_data = NULL; goto err; } - wn->private_data = pad; wn->min_iqs = pad->bytes_per_frame; goto again; } @@ -340,37 +340,9 @@ err: return ret; } -__malloc static void *alsa_parse_config_or_die(int argc, char **argv) -{ - struct alsa_write_args_info *conf = para_calloc(sizeof(*conf)); - - /* exits on errors */ - alsa_write_cmdline_parser(argc, argv, conf); - return conf; -} - -static void alsa_free_config(void *conf) -{ - alsa_write_cmdline_parser_free(conf); -} - -/** - * The init function of the alsa writer. - * - * \param w Pointer to the writer to initialize. - * - * \sa struct \ref writer. - */ -void alsa_write_init(struct writer *w) -{ - struct alsa_write_args_info dummy; +struct writer lsg_write_cmd_com_alsa_user_data = { - alsa_write_cmdline_parser_init(&dummy); - w->close = alsa_close; - w->pre_select = alsa_write_pre_select; - w->post_select = alsa_write_post_select; - w->parse_config_or_die = alsa_parse_config_or_die; - w->free_config = alsa_free_config; - w->help = (struct ggo_help)DEFINE_GGO_HELP(alsa_write); - alsa_write_cmdline_parser_free(&dummy); -} + .pre_select = alsa_write_pre_select, + .post_select = alsa_write_post_select, + .close = alsa_close, +}; diff --git a/amp_filter.c b/amp_filter.c index 5193d7c1..f3d0d87d 100644 --- a/amp_filter.c +++ b/amp_filter.c @@ -7,12 +7,12 @@ /** \file amp_filter.c Paraslash's amplify filter. */ #include +#include +#include "filter_cmd.lsg.h" #include "para.h" -#include "amp_filter.cmdline.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "string.h" @@ -31,33 +31,18 @@ static void amp_close(struct filter_node *fn) free(fn->private_data); } -static int amp_parse_config(int argc, char **argv, void **config) -{ - struct amp_filter_args_info *conf = para_calloc(sizeof(*conf)); - int ret; - - amp_filter_cmdline_parser(argc, argv, conf); - ret = -ERRNO_TO_PARA_ERROR(EINVAL); - if (conf->amp_arg < 0) - goto err; - *config = conf; - return 1; -err: - free(conf); - return ret; -} - static void amp_open(struct filter_node *fn) { struct private_amp_data *pad = para_calloc(sizeof(*pad)); - struct amp_filter_args_info *conf = fn->conf; + unsigned given = FILTER_CMD_OPT_GIVEN(AMP, AMP, fn->lpr); + uint32_t amp_arg = FILTER_CMD_OPT_UINT32_VAL(AMP, AMP, fn->lpr); fn->private_data = pad; fn->min_iqs = 2; - if (!conf->amp_given && stat_item_values[SI_AMPLIFICATION]) + if (!given && stat_item_values[SI_AMPLIFICATION]) sscanf(stat_item_values[SI_AMPLIFICATION], "%u", &pad->amp); else - pad->amp = conf->amp_arg; + pad->amp = amp_arg; PARA_INFO_LOG("amplification: %u (scaling factor: %1.2f)\n", pad->amp, pad->amp / 64.0 + 1.0); } @@ -116,26 +101,9 @@ err: return ret; } -static void amp_free_config(void *conf) -{ - amp_filter_cmdline_parser_free(conf); -} - -/** - * The init function of the amplify filter. - * - * \param f Pointer to the struct to initialize. - */ -void amp_filter_init(struct filter *f) -{ - struct amp_filter_args_info dummy; - - amp_filter_cmdline_parser_init(&dummy); - f->open = amp_open; - f->close = amp_close; - f->pre_select = generic_filter_pre_select; - f->post_select = amp_post_select; - f->parse_config = amp_parse_config; - f->free_config = amp_free_config; - f->help = (struct ggo_help)DEFINE_GGO_HELP(amp_filter); -} +const struct filter lsg_filter_cmd_com_amp_user_data = { + .open = amp_open, + .close = amp_close, + .pre_select = generic_filter_pre_select, + .post_select = amp_post_select, +}; diff --git a/ao_write.c b/ao_write.c index 82d98f3e..e469a394 100644 --- a/ao_write.c +++ b/ao_write.c @@ -9,17 +9,16 @@ #include #include #include +#include +#include "write_cmd.lsg.h" #include "para.h" #include "fd.h" #include "string.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "write.h" -#include "write_common.h" -#include "ao_write.cmdline.h" #include "error.h" struct private_aow_data { @@ -44,6 +43,7 @@ static void aow_close(struct writer_node *wn) ao_close(pawd->dev); free(pawd); wn->private_data = NULL; + ao_shutdown(); } static void aow_pre_select(struct sched *s, void *context) @@ -147,6 +147,38 @@ static int aow_open_device(int id, ao_sample_format *asf, ao_option *options, return -E_AO_OPEN_LIVE; } +static void aow_show_drivers(void) +{ + int i, j, num_drivers; + ao_info **driver_list; + + PARA_DEBUG_LOG("libao drivers available on this host:\n"); + PARA_DEBUG_LOG("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + + driver_list = ao_driver_info_list(&num_drivers); + + for (i = 0; i < num_drivers; i++) { + ao_info *info = driver_list[i]; + char *keys = NULL, *tmp = NULL; + + if (info->type == AO_TYPE_FILE) + continue; + PARA_DEBUG_LOG("%s: %s", info->short_name, info->name); + PARA_DEBUG_LOG("priority: %d", info->priority); + for (j = 0; j < info->option_count; j++) { + tmp = make_message("%s%s%s", keys? keys : "", + keys? ", " : "", + info->options[j]); + free(keys); + keys = tmp; + } + PARA_DEBUG_LOG("keys: %s", keys? keys : "[none]"); + free(keys); + PARA_DEBUG_LOG("comment: %s", info->comment? + info->comment : "[none]"); + } +} + static int aow_init(struct writer_node *wn, unsigned sample_rate, unsigned channels, int sample_format) { @@ -154,12 +186,15 @@ static int aow_init(struct writer_node *wn, unsigned sample_rate, ao_option *aoo = NULL; ao_sample_format asf; ao_info *info; + const struct lls_opt_result *r; + unsigned n; struct private_aow_data *pawd = para_malloc(sizeof(*pawd)); - struct ao_write_args_info *conf = wn->conf; - if (conf->driver_given) { + ao_initialize(); + aow_show_drivers(); + if (WRITE_CMD_OPT_GIVEN(AO, DRIVER, wn->lpr)) { ret = -E_AO_BAD_DRIVER; - id = ao_driver_id(conf->driver_arg); + id = ao_driver_id(WRITE_CMD_OPT_STRING_VAL(AO, DRIVER, wn->lpr)); } else { ret = -E_AO_DEFAULT_DRIVER; id = ao_default_driver_id(); @@ -173,8 +208,11 @@ static int aow_init(struct writer_node *wn, unsigned sample_rate, goto fail; } PARA_INFO_LOG("using %s driver\n", info->short_name); - for (i = 0; i < conf->ao_option_given; i++) { - char *o = para_strdup(conf->ao_option_arg[i]), *value; + r = WRITE_CMD_OPT_RESULT(AO, AO_OPTION, wn->lpr); + n = lls_opt_given(r); + for (i = 0; i < n; i++) { + char *o = para_strdup(lls_string_val(i, r)); + char *value; ret = -E_AO_BAD_OPTION; value = strchr(o, ':'); @@ -379,78 +417,9 @@ out: return ret; } -__malloc static void *aow_parse_config_or_die(int argc, char **argv) -{ - struct ao_write_args_info *conf = para_calloc(sizeof(*conf)); - - /* exits on errors */ - ao_write_cmdline_parser(argc, argv, conf); - return conf; -} - -static void aow_free_config(void *conf) -{ - ao_write_cmdline_parser_free(conf); -} - -/** - * The init function of the ao writer. - * - * \param w Pointer to the writer to initialize. - * - * \sa struct writer. - */ -void ao_write_init(struct writer *w) -{ - struct ao_write_args_info dummy; - int i, j, num_drivers, num_lines; - ao_info **driver_list; - char **dh; /* detailed help */ - - ao_write_cmdline_parser_init(&dummy); - w->close = aow_close; - w->pre_select = aow_pre_select; - w->post_select = aow_post_select; - w->parse_config_or_die = aow_parse_config_or_die; - w->free_config = aow_free_config; - w->help = (struct ggo_help)DEFINE_GGO_HELP(ao_write); - /* create detailed help containing all supported drivers/options */ - for (i = 0; ao_write_args_info_detailed_help[i]; i++) - ; /* nothing */ - num_lines = i; - dh = para_malloc((num_lines + 3) * sizeof(char *)); - for (i = 0; i < num_lines; i++) - dh[i] = para_strdup(ao_write_args_info_detailed_help[i]); - dh[num_lines++] = para_strdup("libao drivers available on this host:"); - dh[num_lines++] = para_strdup("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); - - ao_initialize(); - driver_list = ao_driver_info_list(&num_drivers); - - for (i = 0; i < num_drivers; i++) { - ao_info *info = driver_list[i]; - char *keys = NULL, *tmp = NULL; - - if (info->type == AO_TYPE_FILE) - continue; - for (j = 0; j < info->option_count; j++) { - tmp = make_message("%s%s%s", keys? keys : "", - keys? ", " : "", - info->options[j]); - free(keys); - keys = tmp; - } - dh = para_realloc(dh, (num_lines + 6) * sizeof(char *)); - dh[num_lines++] = make_message("%s: %s", info->short_name, info->name); - dh[num_lines++] = make_message("priority: %d", info->priority); - dh[num_lines++] = make_message("keys: %s", keys? keys : "[none]"); - dh[num_lines++] = make_message("comment: %s", info->comment? - info->comment : "[none]"); - dh[num_lines++] = para_strdup(NULL); - free(keys); - } - dh[num_lines] = NULL; - w->help.detailed_help = (const char **)dh; - ao_write_cmdline_parser_free(&dummy); -} +struct writer lsg_write_cmd_com_ao_user_data = { + .close = aow_close, + .pre_select = aow_pre_select, + .post_select = aow_post_select, +}; diff --git a/attribute.c b/attribute.c index 4cb29828..637e1f51 100644 --- a/attribute.c +++ b/attribute.c @@ -8,7 +8,9 @@ #include #include +#include +#include "server_cmd.lsg.h" #include "para.h" #include "error.h" #include "crypt.h" @@ -106,30 +108,16 @@ int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum) return 1; } -/** - * Flags used by the lsatt command. - * - * \param \sa com_lsatt(). - */ -enum lsatt_flags { - /** Whether "-a" was given for the lsatt command. */ - LSATT_FLAG_SORT_BY_ID = 1, - /** Whether "-l" was given for the lsatt command. */ - LSATT_FLAG_LONG = 2, - /** Reverse sort order. */ - LSATT_FLAG_REVERSE = 4 -}; - /** Data passed to the action function of lsatt */ static int print_attribute(struct osl_table *table, struct osl_row *row, const char *name, void *data) { struct afs_callback_arg *aca = data; - unsigned flags = *(unsigned *)aca->query.data; + bool l_given = SERVER_CMD_OPT_GIVEN(LSATT, LONG, aca->lpr); struct osl_object bitnum_obj; int ret; - if (!(flags & LSATT_FLAG_LONG)) { + if (!l_given) { para_printf(&aca->pbout, "%s\n", name); return 1; } @@ -145,21 +133,27 @@ static int print_attribute(struct osl_table *table, struct osl_row *row, static int com_lsatt_callback(struct afs_callback_arg *aca) { - unsigned flags = *(unsigned *)aca->query.data; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LSATT); + bool i_given, r_given; int ret; struct pattern_match_data pmd = { .table = attribute_table, .loop_col_num = ATTCOL_NAME, .match_col_num = ATTCOL_NAME, - .patterns = {.data = (char *)aca->query.data + sizeof(flags), - .size = aca->query.size - sizeof(flags)}, .pm_flags = PM_NO_PATTERN_MATCHES_EVERYTHING, .data = aca, .action = print_attribute }; - if (flags & LSATT_FLAG_SORT_BY_ID) + + ret = lls(lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr)); + assert(ret >= 0); + pmd.lpr = aca->lpr; + i_given = SERVER_CMD_OPT_GIVEN(LSATT, ID_SORT, aca->lpr); + r_given = SERVER_CMD_OPT_GIVEN(LSATT, REVERSE, aca->lpr); + + if (i_given) pmd.loop_col_num = ATTCOL_BITNUM; - if (flags & LSATT_FLAG_REVERSE) + if (r_given) pmd.pm_flags |= PM_REVERSE_LOOP; ret = for_each_matching_row(&pmd); if (ret < 0) @@ -167,70 +161,49 @@ static int com_lsatt_callback(struct afs_callback_arg *aca) if (pmd.num_matches == 0) ret = -E_NO_MATCH; out: + lls_free_parse_result(aca->lpr, cmd); return ret; } -int com_lsatt(struct command_context *cc) +static int com_lsatt(struct command_context *cc, struct lls_parse_result *lpr) { - unsigned flags = 0; - struct osl_object options = {.data = &flags, .size = sizeof(flags)}; - int i; - - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-i")) { - flags |= LSATT_FLAG_SORT_BY_ID; - continue; - } - if (!strcmp(arg, "-l")) { - flags |= LSATT_FLAG_LONG; - continue; - } - if (!strcmp(arg, "-r")) { - flags |= LSATT_FLAG_REVERSE; - continue; - } - } - return send_option_arg_callback_request(&options, cc->argc - i, cc->argv + i, - com_lsatt_callback, afs_cb_result_handler, cc); + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LSATT); + return send_lls_callback_request(com_lsatt_callback, cmd, lpr, cc); } +EXPORT_SERVER_CMD_HANDLER(lsatt); struct addatt_event_data { const char *name; unsigned char bitnum; }; - static int com_addatt_callback(struct afs_callback_arg *aca) { - char *p; - int ret = 1; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADDATT); + int i, ret = 1; size_t len; + unsigned num_inputs; - for ( - p = aca->query.data; - p < (char *)aca->query.data + aca->query.size; - p += len + 1 - ) { + ret = lls(lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr)); + assert(ret >= 0); + num_inputs = lls_num_inputs(aca->lpr); + for (i = 0; i < num_inputs; i++) { + const char *name = lls_input(i, aca->lpr); struct osl_object objs[NUM_ATT_COLUMNS]; struct osl_row *row; unsigned char bitnum; struct addatt_event_data aed; - len = strlen(p); - if (!len || p[len - 1] == '-' || p[len - 1] == '+') { - para_printf(&aca->pbout, "invalid attribute name: %s\n", p); + len = strlen(name); + if (len == 0 || name[len - 1] == '-' || name[len - 1] == '+') { + para_printf(&aca->pbout, + "invalid attribute name: %s\n", name); continue; } - ret = get_attribute_bitnum_by_name(p, &bitnum); + ret = get_attribute_bitnum_by_name(name, &bitnum); if (ret >= 0) { - para_printf(&aca->pbout, "attribute \"%s\" already exists\n", p); + para_printf(&aca->pbout, + "attribute \"%s\" already exists\n", name); continue; } if (ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) /* error */ @@ -251,12 +224,12 @@ static int com_addatt_callback(struct afs_callback_arg *aca) ret = -E_ATT_TABLE_FULL; goto out; } - objs[ATTCOL_NAME].data = p; + objs[ATTCOL_NAME].data = (char *)name; objs[ATTCOL_NAME].size = len + 1; ret = osl(osl_add_row(attribute_table, objs)); if (ret < 0) goto out; - aed.name = p; + aed.name = name; aed.bitnum = bitnum; ret = afs_event(ATTRIBUTE_ADD, &aca->pbout, &aed); if (ret < 0) @@ -265,53 +238,67 @@ static int com_addatt_callback(struct afs_callback_arg *aca) } out: if (ret < 0) - para_printf(&aca->pbout, "%s: %s\n", p, para_strerror(-ret)); + para_printf(&aca->pbout, "error while adding %s\n", + lls_input(i, aca->lpr)); return ret; } -int com_addatt(struct command_context *cc) +static int com_addatt(struct command_context *cc, struct lls_parse_result *lpr) { - int ret; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADDATT); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 1, 64, &errctx)); - if (cc->argc < 2) - return -E_ATTR_SYNTAX; - ret = send_standard_callback_request(cc->argc - 1, cc->argv + 1, - com_addatt_callback, afs_cb_result_handler, cc); - if (ret < 0) - send_strerror(cc, -ret); - return ret; + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(com_addatt_callback, cmd, lpr, cc); } +EXPORT_SERVER_CMD_HANDLER(addatt); static int com_mvatt_callback(struct afs_callback_arg *aca) { - char *old = aca->query.data; - size_t size = strlen(old) + 1; - char *new = old + size; - struct osl_object obj = {.data = old, .size = size}; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(MVATT); + const char *old, *new; + struct osl_object obj; struct osl_row *row; int ret; + ret = lls(lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr)); + assert(ret >= 0); + old = lls_input(0, aca->lpr); + new = lls_input(1, aca->lpr); + obj.data = (char *)old; + obj.size = strlen(old) + 1; ret = osl(osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row)); if (ret < 0) goto out; - obj.data = new; + obj.data = (char *)new; obj.size = strlen(new) + 1; + /* The update fails if the destination attribute exists. */ ret = osl(osl_update_object(attribute_table, row, ATTCOL_NAME, &obj)); out: if (ret < 0) para_printf(&aca->pbout, "cannot rename %s to %s\n", old, new); else ret = afs_event(ATTRIBUTE_RENAME, &aca->pbout, NULL); + lls_free_parse_result(aca->lpr, cmd); return ret; } -int com_mvatt(struct command_context *cc) +static int com_mvatt(struct command_context *cc, struct lls_parse_result *lpr) { - if (cc->argc != 3) - return -E_ATTR_SYNTAX; - return send_standard_callback_request(cc->argc - 1, cc->argv + 1, - com_mvatt_callback, afs_cb_result_handler, cc); + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(MVATT); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 2, 2, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(com_mvatt_callback, cmd, lpr, cc); } +EXPORT_SERVER_CMD_HANDLER(mvatt); static int remove_attribute(struct osl_table *table, struct osl_row *row, const char *name, void *data) @@ -336,31 +323,41 @@ static int remove_attribute(struct osl_table *table, struct osl_row *row, static int com_rmatt_callback(struct afs_callback_arg *aca) { + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RMATT); int ret; struct pattern_match_data pmd = { .table = attribute_table, - .patterns = aca->query, .loop_col_num = ATTCOL_BITNUM, .match_col_num = ATTCOL_NAME, .data = aca, .action = remove_attribute }; + ret = lls(lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr)); + assert(ret >= 0); + pmd.lpr = aca->lpr; ret = for_each_matching_row(&pmd); if (ret < 0) goto out; if (pmd.num_matches == 0) ret = -E_NO_MATCH; out: + lls_free_parse_result(aca->lpr, cmd); return ret; } -int com_rmatt(struct command_context *cc) +static int com_rmatt(struct command_context *cc, struct lls_parse_result *lpr) { - if (cc->argc < 2) - return -E_ATTR_SYNTAX; - return send_standard_callback_request(cc->argc - 1, cc->argv + 1, - com_rmatt_callback, afs_cb_result_handler, cc); + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RMATT); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx)); + + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(com_rmatt_callback, cmd, lpr, cc); } +EXPORT_SERVER_CMD_HANDLER(rmatt); /** * Return a binary representation of the given attribute value. diff --git a/audioc.c b/audioc.c index f8fd80fa..38a8db5d 100644 --- a/audioc.c +++ b/audioc.c @@ -14,23 +14,30 @@ #include #include #include +#include + +#include "audiod_cmd.lsg.h" +#include "audioc.lsg.h" -#include "audioc.cmdline.h" #include "para.h" #include "error.h" #include "net.h" #include "string.h" #include "fd.h" -#include "ggo.h" #include "version.h" /** Array of error strings. */ DEFINE_PARA_ERRLIST; -/** The gengetopt structure containing command line args. */ -static struct audioc_args_info conf; static char *socket_name; +static struct lls_parse_result *lpr; +#define CMD_PTR (lls_cmd(0, audioc_suite)) +#define OPT_RESULT(_name) \ + (lls_opt_result(LSG_AUDIOC_PARA_AUDIOC_OPT_ ## _name, lpr)) +#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name))) +#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name))) +#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name))) static int loglevel; INIT_STDERR_LOGGING(loglevel); @@ -41,7 +48,8 @@ static char *concat_args(unsigned argc, char * const *argv) char *buf = NULL; for (i = 0; i < argc; i++) { - buf = para_strcat(buf, argv[i]); + const char *arg = argv? argv[i] : lls_input(i, lpr); + buf = para_strcat(buf, arg); if (i != argc - 1) buf = para_strcat(buf, "\n"); } @@ -72,7 +80,6 @@ fail: #include "sched.h" #include "buffer_tree.h" #include "interactive.h" -#include "audiod.completion.h" static struct sched sched; @@ -100,7 +107,7 @@ static void help_completer(struct i9e_completion_info *ci, static void version_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-v", NULL}; + char *opts[] = {LSG_AUDIOD_CMD_VERSION_OPTS, NULL}; if (ci->word_num <= 2 && ci->word && ci->word[0] == '-') i9e_complete_option(opts, ci, cr); @@ -110,7 +117,7 @@ static void stat_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { char *sia[] = {STATUS_ITEM_ARRAY NULL}; - char *opts[] = {"-p", NULL}; + char *opts[] = {LSG_AUDIOD_CMD_STAT_OPTS, NULL}; if (ci->word_num <= 2 && ci->word && ci->word[0] == '-') i9e_complete_option(opts, ci, cr); @@ -121,12 +128,16 @@ static void stat_completer(struct i9e_completion_info *ci, static void grab_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-ms", "-ms", "-ma", "-p=", "-n=", "-o", NULL}; + char *opts[] = {LSG_AUDIOD_CMD_GRAB_OPTS, NULL}; i9e_complete_option(opts, ci, cr); } +I9E_DUMMY_COMPLETER(SUPERCOMMAND_UNAVAILABLE); static struct i9e_completer audiod_completers[] = { - AUDIOD_COMPLETERS +#define LSG_AUDIOD_CMD_CMD(_name) {.name = #_name, \ + .completer = _name ## _completer} + LSG_AUDIOD_CMD_SUBCOMMANDS +#undef LSG_AUDIOD_CMD_CMD {.name = NULL} }; @@ -145,13 +156,15 @@ static int audioc_post_select(struct sched *s, void *context) char *buf = NULL; struct audioc_task *at = context; int ret = btr_node_status(at->btrn, 0, BTR_NT_ROOT); + size_t bufsize; if (ret < 0) goto out; if (!FD_ISSET(at->fd, &s->rfds)) return 0; - buf = para_malloc(conf.bufsize_arg); - ret = recv_bin_buffer(at->fd, buf, conf.bufsize_arg); + bufsize = PARA_MAX(1024U, OPT_UINT32_VAL(BUFSIZE)); + buf = para_malloc(bufsize); + ret = recv_bin_buffer(at->fd, buf, bufsize); PARA_DEBUG_LOG("recv: %d\n", ret); if (ret == 0) ret = -E_AUDIOC_EOF; @@ -172,28 +185,26 @@ static struct audioc_task audioc_task, *at = &audioc_task; static int audioc_i9e_line_handler(char *line) { - char *args = NULL; - int ret; + int argc, ret; + char *args, **argv; PARA_DEBUG_LOG("line: %s\n", line); - ret = create_argv(line, " ", &conf.inputs); + ret = create_argv(line, " ", &argv); if (ret < 0) return ret; - conf.inputs_num = ret; - args = concat_args(conf.inputs_num, conf.inputs); - free_argv(conf.inputs); + argc = ret; + args = concat_args(argc, argv); + free_argv(argv); if (!args) return 0; - conf.inputs_num = 0; /* required for audioc_cmdline_parser_free() */ ret = connect_audiod(socket_name, args); + free(args); if (ret < 0) - goto out; + return ret; at->fd = ret; ret = mark_fd_nonblocking(at->fd); if (ret < 0) goto close; - free(args); - args = NULL; at->btrn = btr_new_node(&(struct btr_node_description) EMBRACE(.name = "audioc line handler")); at->task = task_register(&(struct task_info) { @@ -206,8 +217,6 @@ static int audioc_i9e_line_handler(char *line) return 1; close: close(at->fd); -out: - free(args); return ret; } @@ -223,9 +232,10 @@ __noreturn static void interactive_session(void) .loglevel = loglevel, .completers = audiod_completers, }; + PARA_NOTICE_LOG("\n%s\n", version_text("audioc")); - if (conf.history_file_given) - history_file = para_strdup(conf.history_file_arg); + if (OPT_GIVEN(HISTORY_FILE)) + history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE)); else { char *home = para_homedir(); history_file = make_message("%s/.paraslash/audioc.history", @@ -251,7 +261,7 @@ __noreturn static void interactive_session(void) para_log = stderr_log; out: free(history_file); - audioc_cmdline_parser_free(&conf); + free(socket_name); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); @@ -293,13 +303,19 @@ static char *configfile_exists(void) return NULL; } -__noreturn static void print_help_and_die(void) +static void handle_help_flag(void) { - struct ggo_help h = DEFINE_GGO_HELP(audioc); - bool d = conf.detailed_help_given; + char *help; - ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS); - exit(0); + if (OPT_GIVEN(DETAILED_HELP)) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP)) + help = lls_short_help(CMD_PTR); + else + return; + printf("%s\n", help); + free(help); + exit(EXIT_SUCCESS); } /** @@ -320,54 +336,79 @@ __noreturn static void print_help_and_die(void) */ int main(int argc, char *argv[]) { + const struct lls_command *cmd = CMD_PTR; int ret, fd; - char *cf, *buf = NULL, *args = NULL; + char *cf = NULL, *buf, *args, *errctx = NULL; size_t bufsize; + struct lls_parse_result *lpr1, *lpr2, *lpr3; + unsigned num_inputs; - audioc_cmdline_parser(argc, argv, &conf); - loglevel = get_loglevel_by_name(conf.loglevel_arg); - version_handle_flag("audioc", conf.version_given); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); + ret = lls(lls_parse(argc, argv, cmd, &lpr1, &errctx)); + if (ret < 0) + goto fail; + lpr = lpr1; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + version_handle_flag("audioc", OPT_GIVEN(VERSION)); + handle_help_flag(); cf = configfile_exists(); if (cf) { - struct audioc_cmdline_parser_params params = { - .override = 0, - .initialize = 0, - .check_required = 0, - .check_ambiguity = 0, - .print_errors = 1, - - }; - audioc_cmdline_parser_config_file(cf, &conf, ¶ms); - free(cf); - loglevel = get_loglevel_by_name(conf.loglevel_arg); + void *map; + size_t sz; + int cf_argc; + char **cf_argv; + ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL); + if (ret != -E_EMPTY) { + if (ret < 0) + goto out; + ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, + &errctx)); + para_munmap(map, sz); + if (ret < 0) { + PARA_ERROR_LOG("syntax error in %s\n", cf); + goto out; + } + cf_argc = ret; + ret = lls(lls_parse(cf_argc, cf_argv, cmd, &lpr2, + &errctx)); + lls_free_argv(cf_argv); + if (ret < 0) { + PARA_ERROR_LOG("parse error in %s\n", cf); + goto out; + } + ret = lls(lls_merge(lpr1, lpr2, cmd, &lpr3, &errctx)); + lls_free_parse_result(lpr2, cmd); + if (ret < 0) + goto out; + lls_free_parse_result(lpr1, cmd); + lpr = lpr3; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + } } - if (conf.socket_given) - socket_name = para_strdup(conf.socket_arg); + if (OPT_GIVEN(COMPLETE)) + print_completions(); + if (OPT_GIVEN(SOCKET)) + socket_name = para_strdup(OPT_STRING_VAL(SOCKET)); else { char *hn = para_hostname(); socket_name = make_message("/var/paraslash/audiod_socket.%s", hn); free(hn); } - - if (conf.complete_given) - print_completions(); - - if (conf.inputs_num == 0) + num_inputs = lls_num_inputs(lpr); + if (num_inputs == 0) interactive_session(); - args = concat_args(conf.inputs_num, conf.inputs); + args = concat_args(num_inputs, NULL); ret = connect_audiod(socket_name, args); free(socket_name); + free(args); if (ret < 0) goto out; fd = ret; ret = mark_fd_blocking(STDOUT_FILENO); if (ret < 0) goto out; - bufsize = conf.bufsize_arg; + bufsize = PARA_MAX(1024U, OPT_UINT32_VAL(BUFSIZE)); buf = para_malloc(bufsize); do { size_t n = ret = recv_bin_buffer(fd, buf, bufsize); @@ -375,9 +416,14 @@ int main(int argc, char *argv[]) break; ret = write_all(STDOUT_FILENO, buf, n); } while (ret >= 0); -out: free(buf); - free(args); +out: + lls_free_parse_result(lpr, cmd); + free(cf); +fail: + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/audiod.c b/audiod.c index 32b6895d..74d0ce23 100644 --- a/audiod.c +++ b/audiod.c @@ -15,19 +15,19 @@ #include #include #include +#include +#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" @@ -35,37 +35,45 @@ #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 @@ 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 @@ char *stat_item_values[NUM_STAT_ITEMS] = {NULL}; */ 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; /** @@ -183,22 +185,9 @@ 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; }; @@ -395,58 +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, ¶ms); - 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) @@ -477,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); @@ -489,13 +502,10 @@ static void close_receiver(int slot_num) static void writer_cleanup(struct writer_node *wn) { - struct writer *w; - if (!wn) return; - w = writers + wn->writer_num; - PARA_INFO_LOG("closing %s\n", writer_names[wn->writer_num]); - w->close(wn); + PARA_INFO_LOG("closing %s\n", writer_name(wn->wid)); + writer_get(wn->wid)->close(wn); btr_remove_node(&wn->btrn); task_reap(&wn->task); } @@ -590,17 +600,20 @@ static void open_filters(struct slot_info *s) parent = s->receiver_node->btrn; for (i = 0; i < nf; i++) { char buf[20]; + const char *name; const struct filter *f = filter_get(a->filter_nums[i]); fn = s->fns + i; fn->filter_num = a->filter_nums[i]; fn->conf = a->filter_conf[i]; + fn->lpr = a->filter_lpr[i]; + name = filter_name(fn->filter_num); fn->btrn = btr_new_node(&(struct btr_node_description) - EMBRACE(.name = f->name, .parent = parent, + EMBRACE(.name = name, .parent = parent, .handler = f->execute, .context = fn)); if (f->open) f->open(fn); - sprintf(buf, "%s (slot %d)", f->name, (int)(s - slot)); + sprintf(buf, "%s (slot %d)", name, (int)(s - slot)); fn->task = task_register(&(struct task_info) { .name = buf, .pre_select = f->pre_select, @@ -609,7 +622,7 @@ static void open_filters(struct slot_info *s) }, &sched); parent = fn->btrn; PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n", - audio_formats[s->format], i, nf, f->name, (int)(s - slot)); + audio_formats[s->format], i, nf, name, (int)(s - slot)); } } @@ -625,11 +638,11 @@ static void open_writers(struct slot_info *s) * sizeof(struct writer_node)); for (i = 0; i < a->num_writers; i++) { wn = s->wns + i; - wn->conf = a->writer_conf[i]; - wn->writer_num = a->writer_nums[i]; + wn->wid = a->wids[i]; + wn->lpr = a->writer_lpr[i]; register_writer_node(wn, parent, &sched); PARA_NOTICE_LOG("%s writer started in slot %d\n", - writer_names[a->writer_nums[i]], (int)(s - slot)); + writer_name(a->wids[i]), (int)(s - slot)); } } @@ -639,7 +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); @@ -649,9 +663,9 @@ static int open_receiver(int format) slot_num = ret; rn = para_calloc(sizeof(*rn)); rn->receiver = r; - rn->conf = a->receiver_conf; + rn->lpr = a->receiver_lpr; rn->btrn = btr_new_node(&(struct btr_node_description) - EMBRACE(.name = r->name, .context = rn)); + EMBRACE(.name = name, .context = rn)); ret = r->open(rn); if (ret < 0) { btr_remove_node(&rn->btrn); @@ -662,9 +676,9 @@ static int open_receiver(int format) s->format = format; s->receiver_node = rn; PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n", - audio_formats[format], r->name, slot_num); + audio_formats[format], name, slot_num); rn->task = task_register(&(struct task_info) { - .name = r->name, + .name = name, .pre_select = r->pre_select, .post_select = r->post_select, .context = rn, @@ -827,7 +841,7 @@ static int update_item(int itemnum, char *buf) return 1; } -static int parse_stream_command(const char *txt, char **cmd) +static int parse_stream_command(const char *txt, const char **cmd) { int ret, len; char *re, *p = strchr(txt, ':'); @@ -844,38 +858,41 @@ static int parse_stream_command(const char *txt, char **cmd) return ret; } -static int add_filter(int format, char *cmdline) +static int add_filter(int format, const char *cmdline) { struct audio_format_info *a = &afi[format]; int filter_num, nf = a->num_filters; void *cfg; + struct lls_parse_result *flpr; - filter_num = check_filter_arg(cmdline, &cfg); - if (filter_num < 0) - return filter_num; + filter_num = filter_setup(cmdline, &cfg, &flpr); + a->filter_lpr = para_realloc(a->filter_lpr, + (nf + 1) * sizeof(flpr)); a->filter_conf = para_realloc(a->filter_conf, (nf + 1) * sizeof(void *)); a->filter_nums = para_realloc(a->filter_nums, (nf + 1) * sizeof(unsigned)); + a->filter_nums[nf] = filter_num; a->filter_conf[nf] = cfg; + a->filter_lpr[nf] = flpr; a->num_filters++; PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf, - filter_get(filter_num)->name); + filter_name(filter_num)); return filter_num; } static int parse_writer_args(void) { int i, ret; - char *cmd; + const char *cmd; struct audio_format_info *a; - for (i = 0; i < conf.writer_given; i++) { - void *wconf; - int j, nw, writer_num, af_mask; + for (i = 0; i < OPT_GIVEN(WRITER); i++) { + int j, nw, af_mask; - ret = parse_stream_command(conf.writer_arg[i], &cmd); + ret = parse_stream_command(lls_string_val(i, + OPT_RESULT(WRITER)), &cmd); if (ret < 0) return ret; af_mask = ret; @@ -883,47 +900,45 @@ static int parse_writer_args(void) a = afi + j; if ((af_mask & (1 << j)) == 0) /* no match */ continue; - wconf = check_writer_arg_or_die(cmd, &writer_num); nw = a->num_writers; - a->writer_nums = para_realloc(a->writer_nums, (nw + 1) * sizeof(int)); - a->writer_conf = para_realloc(a->writer_conf, (nw + 1) * sizeof(void *)); - a->writer_nums[nw] = writer_num; - a->writer_conf[nw] = wconf; + a->wids = para_realloc(a->wids, (nw + 1) * sizeof(int)); + a->writer_lpr = para_realloc(a->writer_lpr, + (nw + 1) * sizeof(struct lls_parse_result *)); + a->wids[nw] = check_writer_arg_or_die(cmd, + a->writer_lpr + nw); PARA_INFO_LOG("%s writer #%d: %s\n", audio_formats[j], - nw, writer_names[writer_num]); + nw, writer_name(a->wids[nw])); a->num_writers++; } } /* Use default writer for audio formats which are not yet set up. */ FOR_EACH_AUDIO_FORMAT(i) { - void *writer_conf; - int writer_num; a = afi + i; if (a->num_writers > 0) continue; /* already set up */ - writer_conf = check_writer_arg_or_die(NULL, &writer_num); - a->writer_nums = para_malloc(sizeof(int)); - a->writer_nums[0] = writer_num; - a->writer_conf = para_malloc(sizeof(void *)); - a->writer_conf[0] = writer_conf; a->num_writers = 1; + a->wids = para_malloc(sizeof(int)); + a->writer_lpr = para_malloc(sizeof(struct lls_parse_result *)); + a->wids[0] = check_writer_arg_or_die(NULL, a->writer_lpr); PARA_INFO_LOG("%s writer: %s (default)\n", audio_formats[i], - writer_names[writer_num]); + writer_name(a->wids[0])); } return 1; } static int parse_receiver_args(void) { - int i, ret, receiver_num; - char *cmd = NULL; + int i, ret; + const char *arg; struct audio_format_info *a; - for (i = conf.receiver_given - 1; i >= 0; i--) { - char *arg; + FOR_EACH_AUDIO_FORMAT(i) + afi[i].receiver_num = -1; + for (i = OPT_GIVEN(RECEIVER) - 1; i >= 0; i--) { int j, af_mask; - ret = parse_stream_command(conf.receiver_arg[i], &arg); + ret = parse_stream_command(lls_string_val(i, + OPT_RESULT(RECEIVER)), &arg); if (ret < 0) goto out; af_mask = ret; @@ -937,37 +952,27 @@ static int parse_receiver_args(void) * config here. Since we are iterating backwards, the winning * receiver arg is in fact the first one given. */ - if (a->receiver_conf) - a->receiver->free_config(a->receiver_conf); - a->receiver_conf = check_receiver_arg(arg, &receiver_num); - ret = -E_RECV_SYNTAX; - if (!a->receiver_conf) - goto out; - a->receiver = receivers + receiver_num; + lls_free_parse_result(a->receiver_lpr, RECEIVER_CMD(a)); + a->receiver_num = check_receiver_arg(arg, &a->receiver_lpr); } } /* - * Use the first available receiver with no arguments for those audio - * formats for which no receiver was specified. + * Use the default receiver for those audio formats for which no + * receiver was specified. */ - cmd = para_strdup(receivers[0].name); FOR_EACH_AUDIO_FORMAT(i) { - a = &afi[i]; - if (a->receiver_conf) + a = afi + i; + if (a->receiver_num >= 0) continue; - a->receiver_conf = check_receiver_arg(cmd, &receiver_num); - if (!a->receiver_conf) - return -E_RECV_SYNTAX; - a->receiver = &receivers[receiver_num]; + a->receiver_num = check_receiver_arg(NULL, &a->receiver_lpr); } FOR_EACH_AUDIO_FORMAT(i) { a = afi + i; PARA_INFO_LOG("receiving %s streams via %s receiver\n", - audio_formats[i], a->receiver->name); + audio_formats[i], lls_command_name(RECEIVER_CMD(a))); } ret = 1; out: - free(cmd); return ret; } @@ -977,6 +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; @@ -986,8 +992,7 @@ static int init_default_filters(void) * udp and dccp streams are fec-encoded, so add fecdec as the * first filter. */ - if (strcmp(afi[i].receiver->name, "udp") == 0 || - strcmp(afi[i].receiver->name, "dccp") == 0) { + if (strcmp(name, "udp") == 0 || strcmp(name, "dccp") == 0) { tmp = para_strdup("fecdec"); add_filter(i, tmp); free(tmp); @@ -996,20 +1001,20 @@ static int init_default_filters(void) } /* add "dec" to audio format name */ tmp = make_message("%sdec", audio_formats[i]); - for (j = 0; filter_get(j); j++) - if (!strcmp(tmp, filter_get(j)->name)) + for (j = 1; filter_get(j); j++) + if (!strcmp(tmp, filter_name(j))) break; free(tmp); ret = -E_UNSUPPORTED_FILTER; if (!filter_get(j)) goto out; - tmp = para_strdup(filter_get(j)->name); + tmp = para_strdup(filter_name(j)); ret = add_filter(i, tmp); free(tmp); if (ret < 0) goto out; PARA_INFO_LOG("%s -> default filter: %s\n", audio_formats[i], - filter_get(j)->name); + filter_name(j)); } out: return ret; @@ -1019,9 +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; @@ -1036,7 +1042,7 @@ static int parse_filter_args(void) } if (num_matches == 0) PARA_WARNING_LOG("ignoring filter spec: %s\n", - conf.filter_arg[i]); + lls_string_val(i, OPT_RESULT(FILTER))); } ret = init_default_filters(); /* use default values for the rest */ out: @@ -1060,10 +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", @@ -1071,14 +1077,12 @@ static void init_local_sockets(struct command_task *ct) free(hn); } PARA_NOTICE_LOG("local socket: %s\n", socket_name); - if (conf.force_given) + if (OPT_GIVEN(FORCE)) unlink(socket_name); - ct->fd[0] = create_local_socket(socket_name, 0); - ct->fd[1] = create_local_socket(socket_name, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); - if (ct->fd[0] >= 0 || ct->fd[1] >= 0) + ct->fd = create_local_socket(socket_name); + if (ct->fd >= 0) return; - PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd[1])); + PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd)); exit(EXIT_FAILURE); } @@ -1105,16 +1109,12 @@ static int signal_post_select(struct sched *s, void *context) static void command_pre_select(struct sched *s, void *context) { struct command_task *ct = context; - int i; - - for (i = 0; i < 2; i++) - if (ct->fd[i] >= 0) - para_fd_set(ct->fd[i], &s->rfds, &s->max_fileno); + para_fd_set(ct->fd, &s->rfds, &s->max_fileno); } static int command_post_select(struct sched *s, void *context) { - int ret, i; + int ret; struct command_task *ct = context; static struct timeval last_status_dump; struct timeval tmp, delay; @@ -1123,19 +1123,15 @@ static int command_post_select(struct sched *s, void *context) ret = task_get_notification(ct->task); if (ret < 0) return ret; - for (i = 0; i < 2; i++) { - if (ct->fd[i] < 0) - continue; - ret = handle_connect(ct->fd[i], &s->rfds); - if (ret < 0) { - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - if (ret == -E_AUDIOD_TERM) { - task_notify_all(s, -ret); - return ret; - } - } else if (ret > 0) - force = true; - } + ret = handle_connect(ct->fd, &s->rfds); + if (ret < 0) { + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + if (ret == -E_AUDIOD_TERM) { + task_notify_all(s, -ret); + return ret; + } + } else if (ret > 0) + force = true; if (force == true) goto dump; @@ -1163,7 +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", @@ -1256,7 +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); } @@ -1337,7 +1332,7 @@ static int status_post_select(struct sched *s, void *context) goto out; } close_stat_pipe(); - st->clock_diff_count = conf.clock_diff_count_arg; + st->clock_diff_count = OPT_UINT32_VAL(CLOCK_DIFF_COUNT); goto out; } if (st->ct) { @@ -1405,7 +1400,7 @@ static void init_status_task(struct status_task *st) { memset(st, 0, sizeof(struct status_task)); st->sa_time_diff_sign = 1; - st->clock_diff_count = conf.clock_diff_count_arg; + st->clock_diff_count = OPT_UINT32_VAL(CLOCK_DIFF_COUNT); st->current_audio_format_num = -1; st->btrn = btr_new_node(&(struct btr_node_description) EMBRACE(.name = "stat")); @@ -1421,36 +1416,20 @@ static void init_status_task(struct status_task *st) static void set_initial_status(void) { audiod_status = AUDIOD_ON; - if (!conf.mode_given) + if (!OPT_GIVEN(MODE)) return; - if (!strcmp(conf.mode_arg, "sb")) { + if (!strcmp(OPT_STRING_VAL(MODE), "sb")) { audiod_status = AUDIOD_STANDBY; return; } - if (!strcmp(conf.mode_arg, "off")) { + if (!strcmp(OPT_STRING_VAL(MODE), "off")) { audiod_status = AUDIOD_OFF; return; } - if (strcmp(conf.mode_arg, "on")) + if (strcmp(OPT_STRING_VAL(MODE), "on")) PARA_WARNING_LOG("invalid mode\n"); } -__noreturn static void print_help_and_die(void) -{ - struct ggo_help h = DEFINE_GGO_HELP(audiod); - bool d = conf.detailed_help_given; - unsigned flags; - - flags = d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS; - ggo_print_help(&h, flags); - - flags = d? GPH_MODULE_FLAGS_DETAILED : GPH_MODULE_FLAGS; - print_receiver_helps(flags); - print_filter_helps(flags); - print_writer_helps(flags); - exit(0); -} - /** * Lookup the given UID in the whitelist. * @@ -1466,14 +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 * @@ -1488,40 +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, ¶ms); - 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(); @@ -1539,7 +1531,7 @@ int main(int argc, char *argv[]) init_status_task(stat_task); init_command_task(cmd_task); - if (conf.daemon_given) + if (OPT_GIVEN(DAEMON)) daemonize(false /* parent exits immediately */); signal_task->task = task_register(&(struct task_info) { @@ -1556,6 +1548,10 @@ int main(int argc, char *argv[]) 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 --git a/audiod.cmd b/audiod.cmd deleted file mode 100644 index 18c802de..00000000 --- a/audiod.cmd +++ /dev/null @@ -1,79 +0,0 @@ -BN: audiod -SF: audiod_command.c -SN: list of audiod commands ---- -N: cycle -D: switch to next mode -U: cycle -H: on -> standby -> off -> on ---- -N: grab -D: grab the audio stream -L: -U: -- grab [-m[{s|p|a}]] [-p=PARENT] [-n=NAME] [-o] -H: -H: grab ('splice') the audio stream at any position in the buffer -H: tree and send that data back to the client. -H: -H: Options: -H: -H: -m Change grab mode. Defaults to sloppy grab if not given. -H: -H: -ms: sloppy grab -H: -mp: pedantic grab -H: -ma: aggressive grab -H: -H: The various grab modes only differ in what happens if an attempt to -H: write the grabbed audio data would block. Sloppy mode ignores the -H: write, pedantic mode aborts and aggressive mode tries to write anyway. -H: -H: -p Grab output of node PARENT of the buffer tree. -H: -H: -n Name of the new buffer tree node. Defaults to 'grab'. -H: -H: -o One-shot mode: Stop grabbing if audio file changes. ---- -N: help -D: display command list or help for given command -U: help [command] -H: When I was younger, so much younger than today, I never needed anybody's help -H: in any way. But now these days are gone, I'm not so self assured. Now I find -H: I've changed my mind and opened up the doors. -H: -- Beatles: Help ---- -N: off -D: deactivate para_audiod -U: off -H: Close connection to para_server and stop all decoders. ---- -N: on -D: activate para_audiod -U: on -H: Establish connection to para_server, retrieve para_server's current status. If -H: playing, start corresponding decoder. Otherwise stop all decoders. ---- -N: sb -D: enter standby mode -U: sb -H: Stop all decoders but leave connection to para_server open. ---- -N: stat -D: print status information -U: stat [-p] [item1 ...] -H: Dump given status items (all if none given) to stdout. If -p is given, use -H: parser-friendly mode. ---- -N: tasks -D: list current tasks -U: tasks -H: Print the list of task ids together with the status of each task. ---- -N: term -D: terminate audiod -U: term -H: Stop all decoders, shut down connection to para_server and exit. ---- -N: version -D: print the version of para_audiod -U: version [-v] -H: If the -v option is given, a more detailed version text is printed. diff --git a/audiod.h b/audiod.h index 7073c6dd..295a02bc 100644 --- a/audiod.h +++ b/audiod.h @@ -13,15 +13,6 @@ enum {AUDIOD_AUDIO_FORMATS_ENUM}; /** array of audio format names supported by para_audiod */ extern const char *audio_formats[]; -/** - * the possible modes of operation - * - * - off: disconnect from para_server - * - on: receive status information from para_server and play the audio stream - * - sb: only receive status information but not the audio stream - */ -enum audiod_status_info {AUDIOD_OFF, AUDIOD_ON, AUDIOD_STANDBY}; - extern int audiod_status; /* defined in audiod.c */ diff --git a/audiod_command.c b/audiod_command.c index 3a390275..9623c4f5 100644 --- a/audiod_command.c +++ b/audiod_command.c @@ -13,13 +13,13 @@ #include #include #include +#include +#include "audiod.lsg.h" #include "para.h" -#include "audiod.cmdline.h" -#include "audiod.command_list.h" +#include "audiod_cmd.lsg.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "grab_client.h" @@ -35,35 +35,23 @@ extern struct sched sched; extern char *stat_item_values[NUM_STAT_ITEMS]; -typedef int audiod_command_handler_t(int, int, char **); -static audiod_command_handler_t AUDIOD_COMMAND_HANDLERS; - -/* Defines one command of para_audiod. */ -struct audiod_command { - const char *name; - /* Pointer to the function that handles the command. */ - /* - * Command handlers must never never close their file descriptor. A - * positive return value tells audiod that the status items have - * changed. In this case audiod will send an updated version of all - * status items to to each connected stat client. - */ - audiod_command_handler_t *handler; - /* One-line description. */ - const char *description; - /* Summary of the command line options. */ - const char *usage; - /* The long help text. */ - const char *help; -}; +/** The maximal number of simultaneous connections. */ +#define MAX_STAT_CLIENTS 50 -static struct audiod_command audiod_cmds[] = {DEFINE_AUDIOD_CMD_ARRAY}; +/** Pointer to a command handler function. */ +typedef int (*audiod_cmd_handler_t)(int, struct lls_parse_result *); -/** Iterate over the array of all audiod commands. */ -#define FOR_EACH_COMMAND(c) for (c = 0; audiod_cmds[c].name; c++) +/** The lopsub user_data pointer. Only the command handler at the moment. */ +struct audiod_command_info { + audiod_cmd_handler_t handler; /**< Implementation of the command. */ +}; -/** The maximal number of simultaneous connections. */ -#define MAX_STAT_CLIENTS 50 +/** Define the user_data pointer as expected by lopsub. */ +#define EXPORT_AUDIOD_CMD_HANDLER(_cmd) \ + /** Implementation of _cmd. */ \ + const struct audiod_command_info lsg_audiod_cmd_com_ ## _cmd ## _user_data = { \ + .handler = com_ ## _cmd \ + }; /** Flags used for the stat command of para_audiod. */ enum stat_client_flags { @@ -245,10 +233,11 @@ static int dump_commands(int fd) char *buf = para_strdup(""), *tmp = NULL; int i; ssize_t ret; + const struct lls_command *cmd; - FOR_EACH_COMMAND(i) { - tmp = make_message("%s%s\t%s\n", buf, audiod_cmds[i].name, - audiod_cmds[i].description); + for (i = 1; (cmd = lls_cmd(i, audiod_cmd_suite)); i++) { + tmp = make_message("%s%s\t%s\n", buf, lls_command_name(cmd), + lls_purpose(cmd)); free(buf); buf = tmp; } @@ -257,76 +246,80 @@ static int dump_commands(int fd) return ret; } -static int com_help(int fd, int argc, char **argv) +static int com_help(int fd, struct lls_parse_result *lpr) { - int i, ret; - char *buf; - - if (argc < 2) { - ret = dump_commands(fd); - goto out; + int ret; + char *buf, *errctx; + const struct lls_command *cmd; + + ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx)); + if (ret < 0) { + if (errctx) { + buf = make_message("%s\n", errctx); + client_write(fd, buf); + free(buf); + free(errctx); + } + return ret; } - FOR_EACH_COMMAND(i) { - if (strcmp(audiod_cmds[i].name, argv[1])) - continue; - buf = make_message( - "NAME\n\t%s -- %s\n" - "SYNOPSIS\n\tpara_audioc %s\n" - "DESCRIPTION\n%s\n", - argv[1], - audiod_cmds[i].description, - audiod_cmds[i].usage, - audiod_cmds[i].help - ); - ret = client_write(fd, buf); + if (lls_num_inputs(lpr) == 0) + return dump_commands(fd); + ret = lls(lls_lookup_subcmd(lls_input(0, lpr), audiod_cmd_suite, + &errctx)); + if (ret < 0) { + buf = make_message("%s: %s\nAvailable commands:\n", errctx? + errctx : lls_input(0, lpr), para_strerror(-ret)); + if (client_write(fd, buf) >= 0) + dump_commands(fd); + free(errctx); free(buf); goto out; } - ret = client_write(fd, "No such command. Available commands:\n"); - if (ret > 0) - ret = dump_commands(fd); + cmd = lls_cmd(ret, audiod_cmd_suite); + buf = lls_long_help(cmd); + assert(buf); + ret = client_write(fd, buf); + free(buf); out: return ret < 0? ret : 0; } +EXPORT_AUDIOD_CMD_HANDLER(help) -static int com_tasks(int fd, __a_unused int argc, __a_unused char **argv) +static int com_tasks(int fd, __a_unused struct lls_parse_result *lpr) { + int ret; char *tl = get_task_list(&sched); - int ret = 1; - if (tl) - ret = client_write(fd, tl); + if (!tl) /* no tasks registered yet */ + return 0; + ret = client_write(fd, tl); free(tl); - return ret < 0? ret : 0; + return ret; } +EXPORT_AUDIOD_CMD_HANDLER(tasks) -static int com_stat(int fd, int argc, char **argv) +static int com_stat(int fd, struct lls_parse_result *lpr) { int i, ret, parser_friendly = 0; uint64_t mask = 0; const uint64_t one = 1; struct para_buffer b = {.flags = 0}; + const struct lls_opt_result *r; + unsigned num_inputs; ret = mark_fd_nonblocking(fd); if (ret < 0) return ret; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strncmp(arg, "-p", 2)) { - parser_friendly = 1; - b.flags = PBF_SIZE_PREFIX; - } + r = lls_opt_result(LSG_AUDIOD_CMD_STAT_OPT_PARSER_FRIENDLY, lpr); + if (lls_opt_given(r) > 0) { + parser_friendly = 1; + b.flags = PBF_SIZE_PREFIX; } - if (i >= argc) + num_inputs = lls_num_inputs(lpr); + if (num_inputs == 0) mask--; /* set all bits */ - for (; i < argc; i++) { - ret = stat_item_valid(argv[i]); + for (i = 0; i < num_inputs; i++) { + ret = stat_item_valid(lls_input(i, lpr)); if (ret < 0) return ret; mask |= (one << ret); @@ -344,58 +337,61 @@ static int com_stat(int fd, int argc, char **argv) free(b.buf); return ret < 0? ret : 0; } +EXPORT_AUDIOD_CMD_HANDLER(stat) -static int com_grab(int fd, int argc, char **argv) +static int com_grab(int fd, struct lls_parse_result *lpr) { - int ret = grab_client_new(fd, argc, argv, &sched); + int ret = grab_client_new(fd, lpr, &sched); return ret < 0? ret : 0; } +EXPORT_AUDIOD_CMD_HANDLER(grab) -static int com_term(__a_unused int fd, __a_unused int argc, __a_unused char **argv) +static int com_term(__a_unused int fd, __a_unused struct lls_parse_result *lpr) { return -E_AUDIOD_TERM; } +EXPORT_AUDIOD_CMD_HANDLER(term) -static int com_on(__a_unused int fd, __a_unused int argc, __a_unused char **argv) +static int com_on(__a_unused int fd, __a_unused struct lls_parse_result *lpr) { audiod_status = AUDIOD_ON; return 1; } +EXPORT_AUDIOD_CMD_HANDLER(on) -static int com_off(__a_unused int fd, __a_unused int argc, __a_unused char **argv) +static int com_off(__a_unused int fd, __a_unused struct lls_parse_result *lpr) { audiod_status = AUDIOD_OFF; return 1; } +EXPORT_AUDIOD_CMD_HANDLER(off) -static int com_sb(__a_unused int fd, __a_unused int argc, __a_unused char **argv) +static int com_sb(__a_unused int fd, __a_unused struct lls_parse_result *lpr) { audiod_status = AUDIOD_STANDBY; return 1; } +EXPORT_AUDIOD_CMD_HANDLER(sb) -static int com_cycle(__a_unused int fd, int argc, char **argv) +static int com_cycle(__a_unused int fd, __a_unused struct lls_parse_result *lpr) { switch (audiod_status) { - case AUDIOD_ON: - return com_sb(fd, argc, argv); - break; - case AUDIOD_OFF: - return com_on(fd, argc, argv); - break; - case AUDIOD_STANDBY: - return com_off(fd, argc, argv); - break; + case AUDIOD_ON: audiod_status = AUDIOD_STANDBY; break; + case AUDIOD_OFF: audiod_status = AUDIOD_ON; break; + case AUDIOD_STANDBY: audiod_status = AUDIOD_OFF; break; } return 1; } +EXPORT_AUDIOD_CMD_HANDLER(cycle) -static int com_version(int fd, int argc, char **argv) +static int com_version(int fd, struct lls_parse_result *lpr) { int ret; char *msg; + const struct lls_opt_result *r_v; - if (argc > 1 && strcmp(argv[1], "-v") == 0) + r_v = lls_opt_result(LSG_AUDIOD_CMD_VERSION_OPT_VERBOSE, lpr); + if (lls_opt_given(r_v)) msg = make_message("%s", version_text("audiod")); else msg = make_message("%s\n", version_single_line("audiod")); @@ -403,6 +399,7 @@ static int com_version(int fd, int argc, char **argv) free(msg); return ret < 0? ret : 0; } +EXPORT_AUDIOD_CMD_HANDLER(version) /** * Handle arriving connections on the local socket. @@ -423,10 +420,14 @@ static int com_version(int fd, int argc, char **argv) * */ int handle_connect(int accept_fd, fd_set *rfds) { - int i, argc, ret, clifd; + int argc, ret, clifd; char buf[MAXLINE], **argv = NULL; struct sockaddr_un unix_addr; uid_t uid; + const struct lls_command *cmd; + struct lls_parse_result *lpr; + char *errctx = NULL; + const struct audiod_command_info *aci; ret = para_accept(accept_fd, rfds, &unix_addr, sizeof(struct sockaddr_un), &clifd); if (ret <= 0) @@ -443,18 +444,27 @@ int handle_connect(int accept_fd, fd_set *rfds) if (ret <= 0) goto out; argc = ret; - //PARA_INFO_LOG("argv[0]: %s, argc = %d\n", argv[0], argc); - FOR_EACH_COMMAND(i) { - if (strcmp(audiod_cmds[i].name, argv[0])) - continue; - ret = audiod_cmds[i].handler(clifd, argc, argv); + ret = lls(lls_lookup_subcmd(argv[0], audiod_cmd_suite, &errctx)); + if (ret < 0) goto out; - } - ret = -E_INVALID_AUDIOD_CMD; + cmd = lls_cmd(ret, audiod_cmd_suite); + ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx)); + if (ret < 0) + goto out; + aci = lls_user_data(cmd); + ret = aci->handler(clifd, lpr); + lls_free_parse_result(lpr, cmd); out: free_argv(argv); if (ret < 0 && ret != -E_CLIENT_WRITE) { - char *tmp = make_message("%s\n", para_strerror(-ret)); + char *tmp; + if (errctx) { + tmp = make_message("%s\n", errctx); + free(errctx); + client_write(clifd, tmp); + free(tmp); + } + tmp = make_message("%s\n", para_strerror(-ret)); client_write(clifd, tmp); free(tmp); } diff --git a/autogen.sh b/autogen.sh index d432adbb..708602e7 100755 --- a/autogen.sh +++ b/autogen.sh @@ -24,5 +24,5 @@ autoheader echo configuring... ./configure $@ > /dev/null echo compiling... -make clean2 > /dev/null 2>&1 +make clean > /dev/null 2>&1 make -j $n > /dev/null diff --git a/bitstream.c b/bitstream.c index 638d19a3..9cd1273c 100644 --- a/bitstream.c +++ b/bitstream.c @@ -19,6 +19,7 @@ #include "error.h" #include "string.h" #include "wma.h" +#include "portable_io.h" #include "bitstream.h" static inline uint32_t get_data(const void *table, int i, int size) diff --git a/bitstream.h b/bitstream.h index 5875b0d0..a6349861 100644 --- a/bitstream.h +++ b/bitstream.h @@ -36,8 +36,8 @@ struct vlc { static inline uint32_t show_bits(struct getbit_context *gbc, int num) { int idx = gbc->index; - const uint8_t *p = gbc->buffer + (idx >> 3); - uint32_t x = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + const char *p = (const char *)gbc->buffer + (idx >> 3); + uint32_t x = read_u32_be(p); return (x << (idx & 7)) >> (32 - num); } diff --git a/blob.c b/blob.c index ed684428..8abecf6a 100644 --- a/blob.c +++ b/blob.c @@ -9,7 +9,9 @@ #include #include #include +#include +#include "server_cmd.lsg.h" #include "para.h" #include "error.h" #include "crypt.h" @@ -90,26 +92,16 @@ INIT_BLOB_TABLE(moods); INIT_BLOB_TABLE(playlists); /** \endcond blob_table */ -/** Flags that may be passed to the \p ls functions of each blob type. */ -enum blob_ls_flags { - /** List both id and name. */ - BLOB_LS_FLAG_LONG = 1, - /** Reverse sort order. */ - BLOB_LS_FLAG_REVERSE = 2, - /** Sort by id instead of name. */ - BLOB_LS_FLAG_SORT_BY_ID = 4, -}; - static int print_blob(struct osl_table *table, struct osl_row *row, const char *name, void *data) { struct afs_callback_arg *aca = data; - uint32_t flags = *(uint32_t *)aca->query.data; + bool l_given = SERVER_CMD_OPT_GIVEN(LSMOOD, LONG, aca->lpr); struct osl_object obj; uint32_t id; int ret; - if (!(flags & BLOB_LS_FLAG_LONG)) { + if (!l_given) { para_printf(&aca->pbout, "%s\n", name); return 0; } @@ -123,68 +115,44 @@ static int print_blob(struct osl_table *table, struct osl_row *row, return 1; } -static int com_lsblob_callback(struct osl_table *table, - struct afs_callback_arg *aca) +static int com_lsblob_callback(const struct lls_command * const cmd, + struct osl_table *table, struct afs_callback_arg *aca) { - uint32_t flags = *(uint32_t *)aca->query.data; + bool i_given, r_given; struct pattern_match_data pmd = { .table = table, - .patterns = {.data = (char *)aca->query.data + sizeof(uint32_t), - .size = aca->query.size - sizeof(uint32_t)}, .pm_flags = PM_NO_PATTERN_MATCHES_EVERYTHING | PM_SKIP_EMPTY_NAME, .match_col_num = BLOBCOL_NAME, .data = aca, .action = print_blob, }; int ret; + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + pmd.lpr = aca->lpr; + assert(ret >= 0); + i_given = SERVER_CMD_OPT_GIVEN(LSMOOD, ID_SORT, aca->lpr); + r_given = SERVER_CMD_OPT_GIVEN(LSMOOD, REVERSE, aca->lpr); - if (flags & BLOB_LS_FLAG_REVERSE) + if (r_given) pmd.pm_flags |= PM_REVERSE_LOOP; - if (!(flags & BLOB_LS_FLAG_SORT_BY_ID)) - pmd.loop_col_num = BLOBCOL_NAME; - else + if (i_given) pmd.loop_col_num = BLOBCOL_ID; + else + pmd.loop_col_num = BLOBCOL_NAME; ret = for_each_matching_row(&pmd); if (ret < 0) goto out; - if (pmd.num_matches == 0 && pmd.patterns.size > 0) + if (pmd.num_matches == 0 && lls_num_inputs(aca->lpr) > 0) ret = -E_NO_MATCH; out: + lls_free_parse_result(aca->lpr, cmd); return ret; } -static int com_lsblob(afs_callback *f, struct command_context *cc) +static int com_lsblob(afs_callback *f, const struct lls_command * const cmd, + struct command_context *cc, struct lls_parse_result *lpr) { - uint32_t flags = 0; - struct osl_object options = {.data = &flags, .size = sizeof(flags)}; - int i; - - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-l")) { - flags |= BLOB_LS_FLAG_LONG; - continue; - } - if (!strcmp(arg, "-i")) { - flags |= BLOB_LS_FLAG_SORT_BY_ID; - continue; - } - if (!strcmp(arg, "-r")) { - flags |= BLOB_LS_FLAG_REVERSE; - continue; - } - break; - } -// if (argc > i) -// return -E_BLOB_SYNTAX; - return send_option_arg_callback_request(&options, cc->argc - i, - cc->argv + i, f, afs_cb_result_handler, cc); + return send_lls_callback_request(f, cmd, lpr, cc); } static int cat_blob(struct osl_table *table, struct osl_row *row, @@ -202,33 +170,42 @@ static int cat_blob(struct osl_table *table, struct osl_row *row, return (ret < 0)? ret : ret2; } -static int com_catblob_callback(struct osl_table *table, - struct afs_callback_arg *aca) +static int com_catblob_callback(const struct lls_command * const cmd, + struct osl_table *table, struct afs_callback_arg *aca) { int ret; struct pattern_match_data pmd = { .table = table, - .patterns = aca->query, .loop_col_num = BLOBCOL_NAME, .match_col_num = BLOBCOL_NAME, .pm_flags = PM_SKIP_EMPTY_NAME, .data = &aca->fd, .action = cat_blob }; + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; ret = for_each_matching_row(&pmd); if (ret < 0) - return ret; + goto out; if (pmd.num_matches == 0) ret = -E_NO_MATCH; +out: + lls_free_parse_result(aca->lpr, cmd); return ret; } -static int com_catblob(afs_callback *f, struct command_context *cc) +static int com_catblob(afs_callback *f, const struct lls_command * const cmd, + struct command_context *cc, struct lls_parse_result *lpr) { - if (cc->argc < 2) - return -E_BLOB_SYNTAX; - return send_standard_callback_request(cc->argc - 1, cc->argv + 1, f, - afs_cb_result_handler, cc); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx)); + + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(f, cmd, lpr, cc); } static int remove_blob(struct osl_table *table, struct osl_row *row, @@ -243,19 +220,21 @@ static int remove_blob(struct osl_table *table, struct osl_row *row, return 1; } -static int com_rmblob_callback(struct osl_table *table, - struct afs_callback_arg *aca) +static int com_rmblob_callback(const struct lls_command * const cmd, + struct osl_table *table, struct afs_callback_arg *aca) { int ret; struct pattern_match_data pmd = { .table = table, - .patterns = aca->query, .loop_col_num = BLOBCOL_NAME, .match_col_num = BLOBCOL_NAME, .pm_flags = PM_SKIP_EMPTY_NAME, .data = aca, .action = remove_blob }; + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; ret = for_each_matching_row(&pmd); if (ret < 0) goto out; @@ -267,19 +246,25 @@ static int com_rmblob_callback(struct osl_table *table, ret = afs_event(BLOB_REMOVE, NULL, table); } out: + lls_free_parse_result(aca->lpr, cmd); return ret; } -static int com_rmblob(afs_callback *f, struct command_context *cc) +static int com_rmblob(afs_callback *f, const struct lls_command * const cmd, + struct command_context *cc, struct lls_parse_result *lpr) { - if (cc->argc < 2) - return -E_MOOD_SYNTAX; - return send_option_arg_callback_request(NULL, cc->argc - 1, cc->argv + 1, f, - afs_cb_result_handler, cc); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx)); + + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(f, cmd, lpr, cc); } -static int com_addblob_callback(struct osl_table *table, - struct afs_callback_arg *aca) +static int com_addblob_callback(__a_unused const struct lls_command * const cmd, + struct osl_table *table, struct afs_callback_arg *aca) { struct osl_object objs[NUM_BLOB_COLUMNS]; char *name = aca->query.data; @@ -399,11 +384,12 @@ again: * the name of the blob to create. The combined buffer is made available to the * afs process via the callback method. */ -static int stdin_command(struct command_context *cc, struct osl_object *arg_obj, - afs_callback *f) +static int stdin_command(struct command_context *cc, + struct lls_parse_result *lpr, afs_callback *f) { struct osl_object query, stdin_obj; int ret; + size_t len = strlen(lls_input(0, lpr)); ret = send_sb(&cc->scc, NULL, 0, SBD_AWAITING_DATA, false); if (ret < 0) @@ -411,11 +397,11 @@ static int stdin_command(struct command_context *cc, struct osl_object *arg_obj, ret = fd2buf(&cc->scc, &stdin_obj); if (ret < 0) return ret; - query.size = arg_obj->size + stdin_obj.size; + query.size = len + 1 + stdin_obj.size; query.data = para_malloc(query.size); - memcpy(query.data, arg_obj->data, arg_obj->size); + memcpy(query.data, lls_input(0, lpr), len + 1); if (stdin_obj.size > 0) - memcpy((char *)query.data + arg_obj->size, stdin_obj.data, + memcpy((char *)query.data + len + 1, stdin_obj.data, stdin_obj.size); free(stdin_obj.data); ret = send_callback_request(f, &query, afs_cb_result_handler, cc); @@ -423,33 +409,42 @@ static int stdin_command(struct command_context *cc, struct osl_object *arg_obj, return ret; } -static int com_addblob(afs_callback *f, struct command_context *cc) +static int com_addblob(afs_callback *f, __a_unused const struct lls_command * const cmd, + struct command_context *cc, struct lls_parse_result *lpr) { - struct osl_object arg_obj; + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); - if (cc->argc != 2) - return -E_BLOB_SYNTAX; - if (!*cc->argv[1]) /* empty name is reserved for the dummy row */ + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + if (!lls_input(0, lpr)[0]) /* empty name is reserved for the dummy row */ return -E_BLOB_SYNTAX; - arg_obj.size = strlen(cc->argv[1]) + 1; - arg_obj.data = (char *)cc->argv[1]; - return stdin_command(cc, &arg_obj, f); + return stdin_command(cc, lpr, f); } -static int com_mvblob_callback(struct osl_table *table, - struct afs_callback_arg *aca) +static int com_mvblob_callback(const struct lls_command * const cmd, + struct osl_table *table, struct afs_callback_arg *aca) { - char *src = (char *)aca->query.data; - struct osl_object obj = {.data = src, .size = strlen(src) + 1}; - char *dest = src + obj.size; + const char *src, *dest; + struct osl_object obj; struct osl_row *row; - int ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row)); + int ret; + + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + src = lls_input(0, aca->lpr); + dest = lls_input(1, aca->lpr); + obj.data = (char *)src; + obj.size = strlen(src) + 1; + ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row)); if (ret < 0) { para_printf(&aca->pbout, "cannot find source blob %s\n", src); goto out; } - obj.data = dest; + obj.data = (char *)dest; obj.size = strlen(dest) + 1; ret = osl(osl_update_object(table, row, BLOBCOL_NAME, &obj)); if (ret < 0) { @@ -459,26 +454,35 @@ static int com_mvblob_callback(struct osl_table *table, } ret = afs_event(BLOB_RENAME, NULL, table); out: + lls_free_parse_result(aca->lpr, cmd); return ret; } -static int com_mvblob(afs_callback *f, struct command_context *cc) +static int com_mvblob(afs_callback *f, const struct lls_command * const cmd, + struct command_context *cc, struct lls_parse_result *lpr) { - if (cc->argc != 3) - return -E_MOOD_SYNTAX; - return send_option_arg_callback_request(NULL, cc->argc - 1, - cc->argv + 1, f, afs_cb_result_handler, cc); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 2, 2, &errctx)); + + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(f, cmd, lpr, cc); } -#define DEFINE_BLOB_COMMAND(cmd_name, table_name, cmd_prefix) \ - static int com_ ## cmd_name ## cmd_prefix ## _callback(struct afs_callback_arg *aca) \ +#define DEFINE_BLOB_COMMAND(cmd_name, c_cmd_name, table_name, short_name, c_short_name) \ + static int com_ ## cmd_name ## short_name ## _callback(struct afs_callback_arg *aca) \ { \ - return com_ ## cmd_name ## blob_callback(table_name ## _table, aca); \ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(c_cmd_name ## c_short_name); \ + return com_ ## cmd_name ## blob_callback(cmd, table_name ## _table, aca); \ } \ - int com_ ## cmd_name ## cmd_prefix(struct command_context *cc) \ + static int com_ ## cmd_name ## short_name(struct command_context *cc, struct lls_parse_result *lpr) \ { \ - return com_ ## cmd_name ## blob(com_ ## cmd_name ## cmd_prefix ## _callback, cc); \ - } + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(c_cmd_name ## c_short_name); \ + return com_ ## cmd_name ## blob(com_ ## cmd_name ## short_name ## _callback, cmd, cc, lpr); \ + } \ + EXPORT_SERVER_CMD_HANDLER(cmd_name ## short_name); static int blob_get_name_by_id(struct osl_table *table, uint32_t id, char **name) @@ -629,25 +633,25 @@ static int blob_open(struct osl_table **table, /** Define all functions for this blob type. */ -#define DEFINE_BLOB_FUNCTIONS(table_name, cmd_prefix) \ +#define DEFINE_BLOB_FUNCTIONS(table_name, short_name, c_short_name) \ DEFINE_BLOB_OPEN(table_name) \ DEFINE_BLOB_CLOSE(table_name) \ DEFINE_BLOB_CREATE(table_name) \ DEFINE_BLOB_INIT(table_name) \ - DEFINE_BLOB_COMMAND(ls, table_name, cmd_prefix) \ - DEFINE_BLOB_COMMAND(cat, table_name, cmd_prefix) \ - DEFINE_BLOB_COMMAND(add, table_name, cmd_prefix) \ - DEFINE_BLOB_COMMAND(rm, table_name, cmd_prefix) \ - DEFINE_BLOB_COMMAND(mv, table_name, cmd_prefix) \ - DEFINE_GET_NAME_BY_ID(table_name, cmd_prefix); \ - DEFINE_GET_DEF_BY_ID(table_name, cmd_prefix); \ - DEFINE_GET_DEF_BY_NAME(table_name, cmd_prefix); \ - DEFINE_GET_NAME_AND_DEF_BY_ROW(table_name, cmd_prefix); \ + DEFINE_BLOB_COMMAND(ls, LS, table_name, short_name, c_short_name) \ + DEFINE_BLOB_COMMAND(cat, CAT, table_name, short_name, c_short_name) \ + DEFINE_BLOB_COMMAND(add, ADD, table_name, short_name, c_short_name) \ + DEFINE_BLOB_COMMAND(rm, RM, table_name, short_name, c_short_name) \ + DEFINE_BLOB_COMMAND(mv, MV, table_name, short_name, c_short_name) \ + DEFINE_GET_NAME_BY_ID(table_name, short_name); \ + DEFINE_GET_DEF_BY_ID(table_name, short_name); \ + DEFINE_GET_DEF_BY_NAME(table_name, short_name); \ + DEFINE_GET_NAME_AND_DEF_BY_ROW(table_name, short_name); \ /* doxygen isn't smart enough to recognize these */ /** \cond blob_function */ -DEFINE_BLOB_FUNCTIONS(lyrics, lyr); -DEFINE_BLOB_FUNCTIONS(images, img); -DEFINE_BLOB_FUNCTIONS(moods, mood); -DEFINE_BLOB_FUNCTIONS(playlists, pl); +DEFINE_BLOB_FUNCTIONS(lyrics, lyr, LYR); +DEFINE_BLOB_FUNCTIONS(images, img, IMG); +DEFINE_BLOB_FUNCTIONS(moods, mood, MOOD); +DEFINE_BLOB_FUNCTIONS(playlists, pl, PL); /** \endcond blob_function */ diff --git a/check_wav.h b/check_wav.h index 0957fe03..045158ab 100644 --- a/check_wav.h +++ b/check_wav.h @@ -29,22 +29,19 @@ struct wav_params { int sample_format_given; }; -/** - * Copy the wav parameters. - * - * \param dst Usually a pointer to struct wav_params. - * \param src Usually a pointer to some args_info struct. - * - * This can not be implemented as a function since the type of the structure - * pointed to by \a src depends on the application. - */ -#define COPY_WAV_PARMS(dst, src) \ - (dst)->channels_arg = (src)->channels_arg; \ - (dst)->channels_given = (src)->channels_given; \ - (dst)->sample_rate_arg = (src)->sample_rate_arg; \ - (dst)->sample_rate_given = (src)->sample_rate_given; \ - (dst)->sample_format_arg = (src)->sample_format_arg; \ - (dst)->sample_format_given = (src)->sample_format_given; +#define LLS_COPY_WAV_PARMS(_dst, _pfx, _lpr) \ + (_dst)->channels_given = lls_opt_given(lls_opt_result( \ + _pfx ## _OPT_CHANNELS, (_lpr))); \ + (_dst)->sample_rate_given = lls_opt_given(lls_opt_result( \ + _pfx ## _OPT_SAMPLE_RATE, (_lpr))); \ + (_dst)->sample_format_given = lls_opt_given(lls_opt_result( \ + _pfx ## _OPT_SAMPLE_FORMAT, (_lpr))); \ + (_dst)->channels_arg = lls_uint32_val(0, lls_opt_result( \ + _pfx ## _OPT_CHANNELS, (_lpr))); \ + (_dst)->sample_rate_arg = lls_uint32_val(0, lls_opt_result( \ + _pfx ## _OPT_SAMPLE_RATE, (_lpr))); \ + (_dst)->sample_format_arg = lls_uint32_val(0, lls_opt_result( \ + _pfx ## _OPT_SAMPLE_FORMAT, (_lpr))); struct check_wav_context *check_wav_init(struct btr_node *parent, struct btr_node *child, struct wav_params *params, diff --git a/client.c b/client.c index f1100df4..68d8a7ef 100644 --- a/client.c +++ b/client.c @@ -8,12 +8,13 @@ #include #include +#include +#include "client.lsg.h" #include "para.h" #include "list.h" #include "sched.h" #include "crypt.h" -#include "client.cmdline.h" #include "string.h" #include "stdin.h" #include "stdout.h" @@ -36,8 +37,7 @@ __printf_2_3 void (*para_log)(int, const char*, ...) = stderr_log; #ifdef HAVE_READLINE #include "interactive.h" -#include "server.completion.h" -#include "afs.completion.h" +#include "server_cmd.lsg.h" struct exec_task { struct task *task; @@ -78,17 +78,52 @@ out: return 0; } -static int make_client_argv(const char *line) +/* Called from the line handler and the completers. This overwrites ct->lpr. */ +static int create_merged_lpr(const char *line) { - int ret; + const struct lls_command *cmd = CLIENT_CMD_PTR; + int argc, ret; + char *cmdline, **argv, *errctx; + struct lls_parse_result *argv_lpr; + static struct lls_parse_result *orig_lpr; - free_argv(ct->conf.inputs); - ret = create_argv(line, " ", &ct->conf.inputs); - if (ret >= 0) - ct->conf.inputs_num = ret; + if (!orig_lpr) + orig_lpr = ct->lpr; + ct->lpr = NULL; + cmdline = make_message("-- %s", line); + ret = create_shifted_argv(cmdline, " ", &argv); + free(cmdline); + if (ret < 0) + return ret; + argc = ret; + if (argc == 2) { /* no words (only blanks in line) */ + free_argv(argv); + return 0; + } + argv[0] = para_strdup("--"); + /* + * The original lpr for the interactive session has no non-option + * arguments. We create a fresh lpr from the words in "line" and merge + * it with the orignal lpr. + */ + ret = lls(lls_parse(argc, argv, cmd, &argv_lpr, &errctx)); + free_argv(argv); + if (ret < 0) + goto fail; + ret = lls(lls_merge(orig_lpr, argv_lpr, cmd, &ct->lpr, &errctx)); + lls_free_parse_result(argv_lpr, cmd); + if (ret < 0) + goto fail; + return 1; +fail: + if (errctx) + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + free(errctx); + assert(ret < 0); return ret; } +/* called from completers */ static int execute_client_command(const char *cmd, char **result) { int ret; @@ -97,9 +132,11 @@ static int execute_client_command(const char *cmd, char **result) .result_buf = para_strdup(""), .result_size = 1, }; + struct lls_parse_result *old_lpr = ct->lpr; + *result = NULL; - ret = make_client_argv(cmd); - if (ret < 0) + ret = create_merged_lpr(cmd); + if (ret <= 0) goto out; exec_task.btrn = btr_new_node(&(struct btr_node_description) EMBRACE(.name = "exec_collect")); @@ -114,6 +151,8 @@ static int execute_client_command(const char *cmd, char **result) goto out; schedule(&command_sched); sched_shutdown(&command_sched); + lls_free_parse_result(ct->lpr, CLIENT_CMD_PTR); + ct->lpr = old_lpr; *result = exec_task.result_buf; btr_remove_node(&exec_task.btrn); ret = 1; @@ -174,7 +213,7 @@ static void complete_lsblob(const char *blob_type, struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-i", "-l", "-r", NULL}; + char *opts[] = {LSG_SERVER_CMD_LSIMG_OPTS, NULL}; if (ci->word[0] == '-') return i9e_complete_option(opts, ci, cr); @@ -218,18 +257,17 @@ static void help_completer(struct i9e_completion_info *ci, result->matches = i9e_complete_commands(ci->word, completers); } -static void version_completer(struct i9e_completion_info *ci, +static void stat_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-v", NULL}; + char *opts[] = {LSG_SERVER_CMD_STAT_OPTS, NULL}; i9e_complete_option(opts, ci, cr); } -static void stat_completer(struct i9e_completion_info *ci, +static void version_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-n=", "-p", NULL}; - //PARA_CRIT_LOG("word: %s\n", ci->word); + char *opts[] = {LSG_SERVER_CMD_VERSION_OPTS, NULL}; i9e_complete_option(opts, ci, cr); } @@ -266,7 +304,7 @@ static void sender_completer(struct i9e_completion_info *ci, static void add_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-a", "-l", "-f", "-v", "--", NULL}; + char *opts[] = {LSG_SERVER_CMD_ADD_OPTS, NULL}; if (ci->word[0] == '-') i9e_complete_option(opts, ci, cr); @@ -276,11 +314,7 @@ static void add_completer(struct i9e_completion_info *ci, static void ls_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = { - "--", "-l", "-l=s", "-l=l", "-l=v", "-l=p", "-l=m", "-l=c", - "-p", "-a", "-r", "-d", "-s=p", "-s=l", "-s=s", "-s=n", "-s=f", - "-s=c", "-s=i", "-s=y", "-s=b", "-s=d", "-s=a", "-F", "-b", NULL - }; + char *opts[] = {LSG_SERVER_CMD_LS_OPTS, NULL}; if (ci->word[0] == '-') i9e_complete_option(opts, ci, cr); cr->filename_completion_desired = true; @@ -323,7 +357,7 @@ out: static void lsatt_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-i", "-l", "-r", NULL}; + char *opts[] = {LSG_SERVER_CMD_LSATT_OPTS, NULL}; i9e_complete_option(opts, ci, cr); } @@ -342,14 +376,14 @@ static void rmatt_completer(struct i9e_completion_info *ci, static void check_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-a", "-m", "-p", NULL}; + char *opts[] = {LSG_SERVER_CMD_CHECK_OPTS, NULL}; i9e_complete_option(opts, ci, cr); } static void rm_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-v", "-f", "-p", NULL}; + char *opts[] = {LSG_SERVER_CMD_RM_OPTS, NULL}; if (ci->word[0] == '-') { i9e_complete_option(opts, ci, cr); @@ -361,7 +395,7 @@ static void rm_completer(struct i9e_completion_info *ci, static void touch_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-n=", "-l=", "-y=", "-i=", "-a=", "-v", "-p", NULL}; + char *opts[] = {LSG_SERVER_CMD_TOUCH_OPTS, NULL}; if (ci->word[0] == '-') i9e_complete_option(opts, ci, cr); @@ -371,7 +405,7 @@ static void touch_completer(struct i9e_completion_info *ci, static void cpsi_completer(struct i9e_completion_info *ci, struct i9e_completion_result *cr) { - char *opts[] = {"-a", "-y", "-i", "-l", "-n", "-v", NULL}; + char *opts[] = {LSG_SERVER_CMD_CPSI_OPTS, NULL}; if (ci->word[0] == '-') i9e_complete_option(opts, ci, cr); @@ -447,9 +481,13 @@ DEFINE_BLOB_COMPLETER(mv, pl) static int client_i9e_line_handler(char *line) { int ret; + static bool first = true; - PARA_DEBUG_LOG("line: %s\n", line); - ret = make_client_argv(line); + if (first) + first = false; + else + lls_free_parse_result(ct->lpr, CLIENT_CMD_PTR); + ret = create_merged_lpr(line); if (ret <= 0) return ret; ret = client_connect(ct, &sched, NULL, NULL); @@ -459,16 +497,18 @@ static int client_i9e_line_handler(char *line) return 1; } +I9E_DUMMY_COMPLETER(SUPERCOMMAND_UNAVAILABLE); static struct i9e_completer completers[] = { - SERVER_COMPLETERS - AFS_COMPLETERS +#define LSG_SERVER_CMD_CMD(_name) {.name = #_name, \ + .completer = _name ## _completer} + LSG_SERVER_CMD_SUBCOMMANDS +#undef LSG_SERVER_CMD_CMD {.name = NULL} }; __noreturn static void interactive_session(void) { int ret; - char *history_file; struct sigaction act; struct i9e_client_info ici = { .fds = {0, 1, 2}, @@ -479,16 +519,15 @@ __noreturn static void interactive_session(void) }; PARA_NOTICE_LOG("\n%s\n", version_text("client")); - if (ct->conf.history_file_given) - history_file = para_strdup(ct->conf.history_file_arg); + if (CLIENT_OPT_GIVEN(HISTORY_FILE, ct->lpr)) + ici.history_file = para_strdup(CLIENT_OPT_STRING_VAL( + HISTORY_FILE, ct->lpr)); else { char *home = para_homedir(); - history_file = make_message("%s/.paraslash/client.history", + ici.history_file = make_message("%s/.paraslash/client.history", home); free(home); } - ici.history_file = history_file; - act.sa_handler = i9e_signal_dispatch; sigemptyset(&act.sa_mask); act.sa_flags = 0; @@ -512,7 +551,9 @@ out: __noreturn static void print_completions(void) { - int ret = i9e_print_completions(completers); + int ret; + + ret = i9e_print_completions(completers); exit(ret <= 0? EXIT_FAILURE : EXIT_SUCCESS); } @@ -587,7 +628,7 @@ int main(int argc, char *argv[]) ret = client_parse_config(argc, argv, &ct, &client_loglevel); if (ret < 0) goto out; - if (ct->conf.complete_given) + if (CLIENT_OPT_GIVEN(COMPLETE, ct->lpr)) print_completions(); if (ret == 0) interactive_session(); /* does not return */ diff --git a/client.h b/client.h index 2f257221..87cfc618 100644 --- a/client.h +++ b/client.h @@ -38,8 +38,8 @@ struct client_task { struct btr_node *btrn[2]; /** The hash value of the decrypted challenge. */ unsigned char *challenge_hash; - /** The configuration (including the command). */ - struct client_args_info conf; + /** The parsed command line (including the command). */ + struct lls_parse_result *lpr; /** The config file for client options. */ char *config_file; /** The RSA private key. */ @@ -52,6 +52,16 @@ struct client_task { char **features; }; +#define CLIENT_CMD_PTR (lls_cmd(0, client_suite)) +#define CLIENT_OPT_RESULT(_name, _lpr) \ + (lls_opt_result(LSG_CLIENT_PARA_CLIENT_OPT_ ## _name, _lpr)) +#define CLIENT_OPT_GIVEN(_name, _lpr) \ + (lls_opt_given(CLIENT_OPT_RESULT(_name, _lpr))) +#define CLIENT_OPT_UINT32_VAL(_name, _lpr) \ + (lls_uint32_val(0, CLIENT_OPT_RESULT(_name, _lpr))) +#define CLIENT_OPT_STRING_VAL(_name, _lpr) \ + (lls_string_val(0, CLIENT_OPT_RESULT(_name, _lpr))) + void client_close(struct client_task *ct); int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr, int *loglevel); diff --git a/client_common.c b/client_common.c index eea7510f..39eb8b4c 100644 --- a/client_common.c +++ b/client_common.c @@ -13,7 +13,9 @@ #include #include #include +#include +#include "client.lsg.h" #include "para.h" #include "error.h" #include "list.h" @@ -23,11 +25,9 @@ #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 @@ void client_close(struct client_task *ct) 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]); @@ -90,7 +90,7 @@ static void client_pre_select(struct sched *s, void *context) else if (ret > 0) para_fd_set(ct->scc.fd, &s->wfds, &s->max_fileno); } - /* fall though */ + /* fallthrough */ case CL_EXECUTING: if (ct->btrn[0]) { ret = btr_node_status(ct->btrn[0], 0, BTR_NT_ROOT); @@ -241,26 +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); @@ -297,8 +294,8 @@ static int client_post_select(struct sched *s, void *context) case CL_RECEIVED_WELCOME: /* send auth command */ if (!FD_ISSET(ct->scc.fd, &s->wfds)) return 0; - sprintf(buf, AUTH_REQUEST_MSG "%s sideband%s", ct->user, - has_feature("aes_ctr128", ct)? ",aes_ctr128" : ""); + sprintf(buf, AUTH_REQUEST_MSG "%s sideband,aes_ctr128", + ct->user); PARA_INFO_LOG("--> %s\n", buf); ret = write_buffer(ct->scc.fd, buf); if (ret < 0) @@ -314,7 +311,6 @@ static int client_post_select(struct sched *s, void *context) /* decrypted challenge/session key buffer */ unsigned char crypt_buf[1024]; struct sb_buffer sbb; - bool use_aes; ret = recv_sb(ct, &s->rfds, &sbb); if (ret <= 0) @@ -333,10 +329,9 @@ static int client_post_select(struct sched *s, void *context) 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; @@ -467,12 +462,12 @@ int client_connect(struct client_task *ct, struct sched *s, struct btr_node *parent, struct btr_node *child) { int ret; + const char *host = CLIENT_OPT_STRING_VAL(HOSTNAME, ct->lpr); + uint32_t port = CLIENT_OPT_UINT32_VAL(SERVER_PORT, ct->lpr); - PARA_NOTICE_LOG("connecting %s:%d\n", ct->conf.hostname_arg, - ct->conf.server_port_arg); + PARA_NOTICE_LOG("connecting %s:%u\n", host, port); ct->scc.fd = -1; - ret = para_connect_simple(IPPROTO_TCP, ct->conf.hostname_arg, - ct->conf.server_port_arg); + ret = para_connect_simple(IPPROTO_TCP, host, port); if (ret < 0) return ret; ct->scc.fd = ret; @@ -498,13 +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); } /** @@ -528,65 +529,90 @@ __noreturn static void print_help_and_die(struct client_task *ct) int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr, int *loglevel) { - char *home = para_homedir(); - int ret; - struct client_task *ct = para_calloc(sizeof(struct client_task)); - - *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, ¶ms)) + 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 --git a/command.c b/command.c index fe4b9232..d0aeea3d 100644 --- a/command.c +++ b/command.c @@ -15,13 +15,14 @@ #include #include #include +#include +#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" @@ -34,34 +35,17 @@ #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 @@ static char *vss_status_tohuman(unsigned int flags) return para_strdup("paused"); } -/* - * return human readable permission string. Never returns NULL. - */ -static char *cmd_perms_itohuman(unsigned int perms) -{ - char *msg = para_malloc(5 * sizeof(char)); - - msg[0] = perms & AFS_READ? 'a' : '-'; - msg[1] = perms & AFS_WRITE? 'A' : '-'; - msg[2] = perms & VSS_READ? 'v' : '-'; - msg[3] = perms & VSS_WRITE? 'V' : '-'; - msg[4] = '\0'; - return msg; -} - /* * Never returns NULL. */ @@ -123,7 +92,7 @@ static char *vss_get_status_flags(unsigned int flags) return msg; } -static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly, +static unsigned get_status(struct misc_meta_data *nmmd, bool parser_friendly, char **result) { char *status, *flags; /* vss status info */ @@ -158,52 +127,6 @@ static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly, return b.offset; } -static int check_sender_args(int argc, char * const * argv, struct sender_command_data *scd) -{ - int i; - - const char *subcmds[] = {SENDER_SUBCOMMANDS NULL}; - scd->sender_num = -1; - if (argc < 3) - return -E_COMMAND_SYNTAX; - for (i = 0; senders[i].name; i++) - if (!strcmp(senders[i].name, argv[1])) - break; - PARA_DEBUG_LOG("%d:%s\n", argc, argv[1]); - if (!senders[i].name) - return -E_COMMAND_SYNTAX; - scd->sender_num = i; - for (i = 0; subcmds[i]; i++) - if (!strcmp(subcmds[i], argv[2])) - break; - if (!subcmds[i]) - return -E_COMMAND_SYNTAX; - scd->cmd_num = i; - if (!senders[scd->sender_num].client_cmds[scd->cmd_num]) - return -E_SENDER_CMD; - switch (scd->cmd_num) { - case SENDER_on: - case SENDER_off: - if (argc != 3) - return -E_COMMAND_SYNTAX; - break; - case SENDER_deny: - case SENDER_allow: - if (argc != 4 || parse_cidr(argv[3], scd->host, - sizeof(scd->host), &scd->netmask) == NULL) - return -E_COMMAND_SYNTAX; - break; - case SENDER_add: - case SENDER_delete: - if (argc != 4) - return -E_COMMAND_SYNTAX; - return parse_fec_url(argv[3], scd); - default: - return -E_COMMAND_SYNTAX; - } - return 1; -} - /** * Send a sideband packet through a blocking file descriptor. * @@ -279,6 +202,81 @@ int send_strerror(struct command_context *cc, int err) 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 @@ 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 : "", @@ -345,11 +343,11 @@ static int com_sender(struct command_context *cc) } 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(); @@ -360,7 +358,8 @@ static int com_sender(struct command_context *cc) 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; } @@ -380,17 +379,16 @@ static int com_sender(struct command_context *cc) } return (i < 10)? 1 : -E_LOCK; } +EXPORT_SERVER_CMD_HANDLER(sender); -/* server info */ -static int com_si(struct command_context *cc) +static int com_si(struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - int ret; char *msg, *ut; + int ret; - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; - mutex_lock(mmd_mutex); ut = daemon_get_uptime_str(now); + mutex_lock(mmd_mutex); ret = xasprintf(&msg, "up: %s\nplayed: %u\n" "server_pid: %d\n" @@ -404,26 +402,27 @@ static int com_si(struct command_context *cc) 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 \ @@ -463,7 +462,7 @@ static int com_version(struct command_context *cc) * * 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; @@ -491,39 +490,16 @@ static unsigned empty_status_items(int parser_friendly, char **result) } #undef EMPTY_STATUS_ITEMS -/* stat */ -static int com_stat(struct command_context *cc) +static int com_stat(struct command_context *cc, struct lls_parse_result *lpr) { - int i, ret; + int ret; struct misc_meta_data tmp, *nmmd = &tmp; char *s; - int32_t num = 0; - int parser_friendly = 0; + 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 @@ -555,114 +531,98 @@ static int com_stat(struct command_context *cc) out: return ret; } +EXPORT_SERVER_CMD_HANDLER(stat); -static int send_list_of_commands(struct command_context *cc, struct server_command *cmd, - const char *handler) +/* 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; @@ -670,12 +630,11 @@ static int com_stop(struct command_context *cc) mutex_unlock(mmd_mutex); return 1; } +EXPORT_SERVER_CMD_HANDLER(stop); -/* pause */ -static int com_pause(struct command_context *cc) +static int com_pause(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; mutex_lock(mmd_mutex); if (!vss_paused() && !vss_stopped()) { mmd->events++; @@ -685,42 +644,44 @@ static int com_pause(struct command_context *cc) mutex_unlock(mmd_mutex); return 1; } +EXPORT_SERVER_CMD_HANDLER(pause); -/* next */ -static int com_next(struct command_context *cc) +static int com_next(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; mutex_lock(mmd_mutex); mmd->events++; mmd->new_vss_status_flags |= VSS_NEXT; mutex_unlock(mmd_mutex); return 1; } +EXPORT_SERVER_CMD_HANDLER(next); -/* nomore */ -static int com_nomore(struct command_context *cc) +static int com_nomore(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; mutex_lock(mmd_mutex); if (vss_playing() || vss_paused()) mmd->new_vss_status_flags |= VSS_NOMORE; mutex_unlock(mmd_mutex); return 1; } +EXPORT_SERVER_CMD_HANDLER(nomore); -/* ff */ -static int com_ff(struct command_context *cc) +static int com_ff(__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 @@ 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 @@ 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) { @@ -807,7 +763,7 @@ 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, @@ -836,7 +792,7 @@ static int parse_auth_request(char *buf, int len, struct user **u, if (strcmp(features[i], "sideband") == 0) continue; if (strcmp(features[i], "aes_ctr128") == 0) - cf->aes_ctr128_requested = true; + continue; else { ret = -E_BAD_FEATURE; goto out; @@ -856,37 +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,10 +963,9 @@ __noreturn void handle_connect(int fd, const char *peername) 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 --git a/command.h b/command.h index 5e1a4ce3..5b01eb79 100644 --- a/command.h +++ b/command.h @@ -6,19 +6,55 @@ struct command_context { const char *peer; /** The paraslash user that executes this command. */ struct user *u; - /** Argument count. */ - int argc; - /** Argument vector. */ - char **argv; /** File descriptor and crypto keys. */ struct stream_cipher_context scc; }; +/** Prototype of a server command handler. */ +typedef int (*server_cmd_handler_t)(struct command_context *, + struct lls_parse_result *); + +/** + * The lopsub user data structure for server commands. + * + * One such structure exists for each server command. Lopsub maintains + * references to the user data structures and provides lls_user_data() for + * applications to fetch the user data pointer of a given command. This + * mechanism is used by the dispatcher of command.c to run the command handler. + */ +struct server_cmd_user_data { + /** Pointer to the command handler. */ + server_cmd_handler_t handler; +}; + +/** Define the user data structure for one command. */ +#define EXPORT_SERVER_CMD_HANDLER(_cmd) \ + const struct server_cmd_user_data lsg_server_cmd_com_ ## _cmd ## _user_data = { \ + .handler = com_ ## _cmd \ + }; + +/** Get the lopsub command pointer by command name. */ +#define SERVER_CMD_CMD_PTR(_cmd) \ + (lls_cmd(LSG_SERVER_CMD_CMD_ ## _cmd, server_cmd_suite)) + +/** Get the lopsub parse result of an option. */ +#define SERVER_CMD_OPT_RESULT(_cmd, _opt, _lpr) \ + (lls_opt_result(LSG_SERVER_CMD_ ## _cmd ## _OPT_ ## _opt, _lpr)) + +/** How many times an option was given. */ +#define SERVER_CMD_OPT_GIVEN(_cmd, _opt, _lpr) \ + (lls_opt_given(SERVER_CMD_OPT_RESULT(_cmd, _opt, _lpr))) + +/** Fetch the (first) argument given to an option of type uint32. */ +#define SERVER_CMD_UINT32_VAL(_cmd, _opt, _lpr) \ + (lls_uint32_val(0, SERVER_CMD_OPT_RESULT(_cmd, _opt, _lpr))) + int send_sb(struct stream_cipher_context *scc, void *buf, size_t numbytes, int band, bool dont_free); __printf_3_4 int send_sb_va(struct stream_cipher_context *scc, int band, const char *fmt, ...); int send_strerror(struct command_context *cc, int err); +int send_errctx(struct command_context *cc, char *errctx); int recv_sb(struct stream_cipher_context *scc, enum sb_designator expected_band, size_t max_size, struct iovec *result); diff --git a/command_util.bash b/command_util.bash deleted file mode 100755 index 5e9dbcc7..00000000 --- a/command_util.bash +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env bash - -read_header() -{ - local key value i - - while read key value; do - case "$key" in - ---) - break - ;; - BN:) - base_name="$value" - ;; - SF:) - source_files="$value" - ;; - SN:) - section_name="$value" - ;; - TM:) - template_members="$value" - esac - done -} - -read_one_command() -{ - local line - - name_txt="" - desc_txt="" - usage_txt="" - help_txt="" - perms_txt="" - template=0 - template_name="" - template_prototype="" - while read key value; do - case "$key" in - ---) - break - ;; - N:) - name_txt="$value" - ;; - T:) - template_name="$value" - template=1 - ;; - O:) - template_prototype="$value" - template=1 - ;; - P:) - perms_txt="$value" - ;; - D:) - desc_txt="$value" - ;; - U:) - usage_txt="$value" - ;; - H:) - help_txt="${value}" - while read line; do - if test "$line" = "---"; then - break; - fi - line=${line#H:} - help_txt="$help_txt -${line# }" - done - break - ;; - esac - done - if test $template -eq 0; then - if test -n "$name_txt" -a -n "$desc_txt" -a -n "$usage_txt" \ - -a -n "$help_txt"; then - ret=1 - return - fi - else - if test -n "$template_name" -a -n "$template_prototype" \ - -a -n "$name_txt " -a -n "$template_members" \ - -a -n "$desc_txt" -a -n "$usage_txt" \ - -a -n "$help_txt"; then - ret=1 - return - fi - fi - if test -z "$name_txt" -a -z "$desc_txt" -a -z "$usage_txt" \ - -a -z "$help_txt"; then - ret=0 - return - fi - ret=-1 - #return - echo "!ERROR!" - echo "N: $name_txt" - echo "D: $desc_txt" - echo "S: $usage_txt" - echo "P: $perms_txt" - echo "H: $help_txt" - echo "O: $template_prototype" -} - -dump_man() -{ - if test $template -eq 0; then - echo ".SS \"$name_txt\"" - echo "$desc_txt" - echo - echo "\\fBUsage: \\fP$usage_txt" - else - for member in $template_members; do - local sed_cmd="sed -e s/@member@/$member/g" - local t_name_txt=$(echo $name_txt | $sed_cmd) - echo ".SS \"$t_name_txt\"" - done - echo "$desc_txt" | sed -e "s/@member@/{$(echo $template_members | sed -e 's/ / | /g')}/g" - echo - echo "\\fBUsage: \\fP" - echo - echo ".nf" - for member in $template_members; do - local sed_cmd="sed -e s/@member@/$member/g" - local t_usage_txt=$(echo $usage_txt | $sed_cmd) - printf "\t$t_usage_txt\n" - done - echo ".fi" - fi - echo - echo "$help_txt" | sed -e 's/^ //' - echo - if test -n "$perms_txt"; then - echo -n "\\fBpermissions:\\fP " - if test "$perms_txt" = "0"; then - echo "(none)" - else - echo "$perms_txt" - fi - fi - echo -} - - -com_man() -{ - echo "[$section_name]" - echo - while : ; do - read_one_command - if test $ret -lt 0; then - exit 1 - fi - if test $ret -eq 0; then - break - fi - dump_man - done -} - -cmd_handler_name() -{ - result="com_$name_txt," -} - -make_array_member() -{ - local TAB=' ' CR=' -' - local tmp - - result="{.name = \"$name_txt\", .handler = com_$name_txt, " - if test -n "$perms_txt"; then - result="$result .perms = $perms_txt," - fi - result="$result.description = \"$desc_txt\", .usage = \"$usage_txt\", \\$CR .help = " - tmp="$(printf "%s\n" "$help_txt" | sed -e 's/^/\"/g' -e 's/$/\\n\"/g' \ - -e "s/$TAB/\\\t/g" -e's/$/\\/g')" - result="$result$tmp$CR}, \\$CR" -} - -make_completion() -{ - local CR=' -' - result=" {.name = \"$name_txt\", .completer = ${name_txt}_completer}, \\$CR" -} - -template_loop() -{ - local loop_result= - - local t_name="$name_txt" - local t_perms="$perms_txt" - local t_desc="$desc_txt" - local t_usage="$usage_txt" - local t_help="$help_txt" - local t_source_files="$source_files" - local member - for member in $template_members; do - name_txt="${t_name//@member@/$member}" - perms_txt="${t_perms//@member@/$member}" - desc_txt="${t_desc//@member@/$member}" - usage_txt="${t_usage//@member@/$member}" - help_txt="${t_help//@member@/$member}" - prototype="${template_prototype//@member@/$member}" - result= - $1 - loop_result="$loop_result$result" - done - result="$loop_result" - # reset global variables - name_txt="$t_name" - perms_txt="$t_perms" - desc_txt="$t_desc" - usage_txt="$t_usage" - help_txt="$t_help" - source_files="$t_source_files" -} - -com_header() -{ - local array_members handlers= CR=' -' - - while : ; do - read_one_command - if test $ret -lt 0; then - exit 1 - fi - if test $ret -eq 0; then - break - fi - if test $template -eq 0; then - cmd_handler_name - handlers+="$result" - make_array_member - array_members="$array_members$result" - continue - fi - template_loop cmd_handler_name - handlers+="$result" - template_loop make_array_member - array_members="$array_members$result" - done - array_members="$array_members{.name = NULL} \\$CR" - echo "#define DEFINE_${base_name^^}_CMD_ARRAY $array_members" - echo "#define ${base_name^^}_COMMAND_HANDLERS ${handlers%,}" -} - -com_completion() -{ - - echo "#define $1 \\" - while : ; do - read_one_command - if test $ret -lt 0; then - exit 1 - fi - if test $ret -eq 0; then - break - fi - if test $template -eq 0; then - make_completion - printf "%s" "$result" - continue - fi - template_loop make_completion - printf "%s" "$result" - done - echo -} - -read_header -arg="$1" -shift -case "$arg" in - "h") - com_header - ;; - "man") - com_man $* - ;; - "compl") - com_completion $* - ;; -esac diff --git a/compress_filter.c b/compress_filter.c index d0d96fd9..65897642 100644 --- a/compress_filter.c +++ b/compress_filter.c @@ -11,23 +11,23 @@ */ #include +#include +#include "filter_cmd.lsg.h" #include "para.h" -#include "compress_filter.cmdline.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "string.h" #include "error.h" +#define U32_OPTVAL(_opt, _lpr) (FILTER_CMD_OPT_UINT32_VAL(COMPRESS, _opt, _lpr)) + /** Data specific to the compress filter. */ struct private_compress_data { /** The current multiplier. */ unsigned current_gain; - /** Points to the configuration data for this instance of the compress filter. */ - struct compress_filter_args_info *conf; /** Maximal admissible gain. */ unsigned max_gain; /** Number of samples already seen. */ @@ -51,9 +51,9 @@ static int compress_post_select(__a_unused struct sched *s, void *context) char *inbuf; size_t length, i; int16_t *ip, *op; - unsigned gain_shift = pcd->conf->inertia_arg + pcd->conf->damp_arg, - mask = (1 << pcd->conf->blocksize_arg) - 1; - + uint32_t inertia = U32_OPTVAL(INERTIA, fn->lpr); + unsigned gain_shift = inertia + U32_OPTVAL(DAMP, fn->lpr), + mask = (1 << U32_OPTVAL(BLOCKSIZE, fn->lpr)) - 1; //inplace = false; next_buffer: ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); @@ -86,7 +86,7 @@ next_buffer: if (sample > 32767) { /* clip */ sample = 32767; pcd->current_gain = (3 * pcd->current_gain + - (1 << pcd->conf->inertia_arg)) / 4; + (1 << inertia)) / 4; pcd->peak = 0; } else if (sample > pcd->peak) pcd->peak = sample; @@ -95,12 +95,12 @@ next_buffer: continue; // PARA_DEBUG_LOG("gain: %u, peak: %u\n", pcd->current_gain, // pcd->peak); - if (pcd->peak < pcd->conf->target_level_arg) { + if (pcd->peak < U32_OPTVAL(TARGET_LEVEL, fn->lpr)) { if (pcd->current_gain < pcd->max_gain) pcd->current_gain++; } else pcd->current_gain = PARA_MAX(pcd->current_gain - 2, - 1U << pcd->conf->inertia_arg); + 1U << inertia); pcd->peak = 0; } if (inplace) @@ -116,47 +116,21 @@ err: return ret; } -/** TODO: Add sanity checks */ -static int compress_parse_config(int argc, char **argv, void **config) -{ - struct compress_filter_args_info *conf = para_calloc(sizeof(*conf)); - - compress_filter_cmdline_parser(argc, argv, conf); - *config = conf; - return 1; -} - static void compress_open(struct filter_node *fn) { - struct private_compress_data *pcd = para_calloc( - sizeof(struct private_compress_data)); - pcd->conf = fn->conf; + struct private_compress_data *pcd = para_calloc(sizeof(*pcd)); + uint32_t inertia = U32_OPTVAL(INERTIA, fn->lpr); + uint32_t aggressiveness = U32_OPTVAL(AGGRESSIVENESS, fn->lpr); + fn->private_data = pcd; fn->min_iqs = 2; /* 16 bit audio */ - pcd->current_gain = 1 << pcd->conf->inertia_arg; - pcd->max_gain = 1 << (pcd->conf->inertia_arg + pcd->conf->aggressiveness_arg); -} - -static void compress_free_config(void *conf) -{ - compress_filter_cmdline_parser_free(conf); + pcd->current_gain = 1U << inertia; + pcd->max_gain = 1U << (inertia + aggressiveness); } -/** - * The init function of the compress filter. - * - * \param f Pointer to the struct to initialize. - */ -void compress_filter_init(struct filter *f) -{ - struct compress_filter_args_info dummy; - - compress_filter_cmdline_parser_init(&dummy); - f->open = compress_open; - f->close = compress_close; - f->pre_select = generic_filter_pre_select; - f->post_select = compress_post_select; - f->parse_config = compress_parse_config; - f->free_config = compress_free_config; - f->help = (struct ggo_help)DEFINE_GGO_HELP(compress_filter); -} +const struct filter lsg_filter_cmd_com_compress_user_data = { + .open = compress_open, + .close = compress_close, + .pre_select = generic_filter_pre_select, + .post_select = compress_post_select, +}; diff --git a/configure.ac b/configure.ac index 0b00cc2a..a589613b 100644 --- a/configure.ac +++ b/configure.ac @@ -51,39 +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]) @@ -92,6 +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 @@ -182,15 +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], []) @@ -213,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 -]], [[ - AudioDeviceID id; -]])],[have_core_audio=yes],[have_core_audio=no]) -AC_MSG_RESULT($have_core_audio) -if test ${have_core_audio} = yes; then - f1="-framework CoreAudio" - f2="-framework AudioToolbox" - f3="-framework AudioUnit" - f4="-framework CoreServices" - core_audio_ldflags="$f1 $f2 $f3 $f4" - AC_SUBST(core_audio_ldflags) - AC_DEFINE(HAVE_CORE_AUDIO, 1, define to 1 on Mac Os X) -fi ########################################################################### ogg STASH_FLAGS LIB_ARG_WITH([ogg], [-logg]) @@ -297,10 +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 @@ -377,19 +347,10 @@ AC_CHECK_HEADER(samplerate.h, [], HAVE_SAMPLERATE=no) AC_CHECK_LIB([samplerate], [src_process], [], HAVE_SAMPLERATE=no) LIB_SUBST_FLAGS(samplerate) UNSTASH_FLAGS -########################################################################## mp4v2 -STASH_FLAGS -LIB_ARG_WITH([mp4v2], [-lmp4v2]) -HAVE_MP4V2=yes -AC_CHECK_HEADER([mp4v2/mp4v2.h], [], [HAVE_MP4V2=no]) -AC_CHECK_LIB([mp4v2], [MP4Read], [], [HAVE_MP4V2=no]) -LIB_SUBST_FLAGS(mp4v2) -UNSTASH_FLAGS ######################################################################### server if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then build_server="yes" executables="$executables server" - server_cmdline_objs="server" server_errlist_objs=" server afh_common @@ -428,7 +389,6 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then wma_common sideband version - ggo " if test "$CRYPTOLIB" = openssl; then server_errlist_objs="$server_errlist_objs crypt" @@ -440,10 +400,10 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then NEED_SPEEX_OBJECTS() && server_errlist_objs="$server_errlist_objs spx_afh spx_common" NEED_OPUS_OBJECTS() && server_errlist_objs="$server_errlist_objs opus_afh opus_common" NEED_FLAC_OBJECTS && server_errlist_objs="$server_errlist_objs flac_afh" - if test $HAVE_FAAD = yes && test $HAVE_MP4V2 = yes; then - server_errlist_objs="$server_errlist_objs aac_afh aac_common" + if test $HAVE_FAAD = yes; then + server_errlist_objs="$server_errlist_objs aac_afh" fi - server_objs="add_cmdline($server_cmdline_objs) $server_errlist_objs" + server_objs="$server_errlist_objs" AC_SUBST(server_objs, add_dot_o($server_objs)) else build_server="no" @@ -452,7 +412,6 @@ fi if test -n "$CRYPTOLIB"; then build_client="yes" executables="$executables client" - client_cmdline_objs="client" client_errlist_objs=" client net @@ -468,7 +427,6 @@ if test -n "$CRYPTOLIB"; then crypt_common base64 version - ggo " if test "$CRYPTOLIB" = openssl; then client_errlist_objs="$client_errlist_objs crypt" @@ -478,8 +436,8 @@ if test -n "$CRYPTOLIB"; then if test $HAVE_READLINE = yes; then client_errlist_objs="$client_errlist_objs interactive" fi - client_objs="add_cmdline($client_cmdline_objs) $client_errlist_objs" - AC_SUBST(client_objs, add_dot_o($client_objs)) + client_objs="$client_errlist_objs" + AC_SUBST(client_objs, add_dot_o($client_errlist_objs)) else build_client="no" fi @@ -488,18 +446,6 @@ if test -n "$CRYPTOLIB"; then build_audiod="yes" executables="$executables audiod" audiod_audio_formats="wma" - audiod_cmdline_objs="$audiod_cmdline_objs - audiod - compress_filter - http_recv - dccp_recv - file_write - client - amp_filter - udp_recv - prebuffer_filter - sync_filter - " audiod_errlist_objs="$audiod_errlist_objs audiod signal @@ -526,7 +472,6 @@ if test -n "$CRYPTOLIB"; then audiod_command fecdec_filter client_common - ggo udp_recv color fec @@ -544,10 +489,6 @@ if test -n "$CRYPTOLIB"; then else audiod_errlist_objs="$audiod_errlist_objs gcrypt" fi - if test "$have_core_audio" = "yes"; then - audiod_errlist_objs="$audiod_errlist_objs osx_write ipc" - audiod_cmdline_objs="$audiod_cmdline_objs osx_write" - fi NEED_VORBIS_OBJECTS && { audiod_errlist_objs="$audiod_errlist_objs oggdec_filter" audiod_audio_formats="$audiod_audio_formats ogg" @@ -565,31 +506,26 @@ if test -n "$CRYPTOLIB"; then audiod_audio_formats="$audiod_audio_formats flac" } if test $HAVE_FAAD = yes; then - audiod_errlist_objs="$audiod_errlist_objs aacdec_filter aac_common" + audiod_errlist_objs="$audiod_errlist_objs aacdec_filter" audiod_audio_formats="$audiod_audio_formats aac" fi if test $HAVE_MAD = yes; then audiod_audio_formats="$audiod_audio_formats mp3" - audiod_cmdline_objs="$audiod_cmdline_objs mp3dec_filter" audiod_errlist_objs="$audiod_errlist_objs mp3dec_filter" fi if test $HAVE_OSS = yes; then audiod_errlist_objs="$audiod_errlist_objs oss_write" - audiod_cmdline_objs="$audiod_cmdline_objs oss_write" fi if test $HAVE_ALSA = yes; then audiod_errlist_objs="$audiod_errlist_objs alsa_write" - audiod_cmdline_objs="$audiod_cmdline_objs alsa_write" fi NEED_AO_OBJECTS && { audiod_errlist_objs="$audiod_errlist_objs ao_write" - audiod_cmdline_objs="$audiod_cmdline_objs ao_write" } if test $HAVE_SAMPLERATE = yes; then audiod_errlist_objs="$audiod_errlist_objs resample_filter check_wav" - audiod_cmdline_objs="$audiod_cmdline_objs resample_filter" fi - audiod_objs="add_cmdline($audiod_cmdline_objs) $audiod_errlist_objs" + audiod_objs="$audiod_errlist_objs" AC_SUBST(audiod_objs, add_dot_o($audiod_objs)) enum="$(for i in $audiod_audio_formats; do printf "AUDIO_FORMAT_${i}, " | tr '[a-z]' '[A-Z]'; done)" @@ -600,53 +536,27 @@ if test -n "$CRYPTOLIB"; then else build_audiod="no" fi -########################################################################### fade +########################################################################### mixer if test $HAVE_OSS = yes -o $HAVE_ALSA = yes; then - build_fade="yes" - executables="$executables fade" - fade_cmdline_objs="fade" - fade_errlist_objs="fade exec string fd version ggo" + build_mixer="yes" + executables="$executables mixer" + mixer_errlist_objs="mixer exec string fd 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 @@ -659,24 +569,14 @@ if test $HAVE_CURSES = yes; then time sched version - ggo " - gui_objs="add_cmdline($gui_cmdline_objs) $gui_errlist_objs" + gui_objs="$gui_errlist_objs" AC_SUBST(gui_objs, add_dot_o($gui_objs)) else build_gui="no" AC_MSG_WARN([no curses lib, cannot build para_gui]) fi ######################################################################## filter -filters=" - compress - wav - amp - fecdec - wmadec - prebuffer - sync -" filter_errlist_objs=" filter_common wav_filter @@ -688,7 +588,6 @@ filter_errlist_objs=" sched fd amp_filter - ggo fecdec_filter fec version @@ -702,65 +601,23 @@ filter_errlist_objs=" net sync_filter " -filter_cmdline_objs=" - filter - compress_filter - amp_filter - prebuffer_filter - sync_filter -" -NEED_VORBIS_OBJECTS && { - filters="$filters oggdec" - filter_errlist_objs="$filter_errlist_objs oggdec_filter" -} -NEED_SPEEX_OBJECTS && { - filters="$filters spxdec" - filter_errlist_objs="$filter_errlist_objs spxdec_filter spx_common" -} -NEED_OPUS_OBJECTS && { - filters="$filters opusdec" - filter_errlist_objs="$filter_errlist_objs opusdec_filter opus_common" -} -NEED_FLAC_OBJECTS && { - filter_errlist_objs="$filter_errlist_objs flacdec_filter" - filters="$filters flacdec" -} +NEED_VORBIS_OBJECTS && filter_errlist_objs="$filter_errlist_objs oggdec_filter" +NEED_SPEEX_OBJECTS && filter_errlist_objs="$filter_errlist_objs spxdec_filter spx_common" +NEED_OPUS_OBJECTS && filter_errlist_objs="$filter_errlist_objs opusdec_filter opus_common" +NEED_FLAC_OBJECTS && filter_errlist_objs="$filter_errlist_objs flacdec_filter" if test $HAVE_FAAD = yes; then - filter_errlist_objs="$filter_errlist_objs aacdec_filter aac_common" - filters="$filters aacdec" + filter_errlist_objs="$filter_errlist_objs aacdec_filter" fi if test $HAVE_MAD = yes; then - filter_cmdline_objs="$filter_cmdline_objs mp3dec_filter" filter_errlist_objs="$filter_errlist_objs mp3dec_filter" - filters="$filters mp3dec" fi if test $HAVE_SAMPLERATE = yes; then filter_errlist_objs="$filter_errlist_objs resample_filter check_wav" - filter_cmdline_objs="$filter_cmdline_objs resample_filter" - filters="$filters resample" fi -filters="$(echo $filters)" -AC_SUBST(filters) -filter_objs="add_cmdline($filter_cmdline_objs) $filter_errlist_objs" +filter_objs="$filter_errlist_objs" AC_SUBST(filter_objs, add_dot_o($filter_objs)) - -enum="$(for i in $filters; do printf "${i}_FILTER, " | tr '[a-z]' '[A-Z]'; done)" -AC_DEFINE_UNQUOTED(FILTER_ENUM, $enum NUM_SUPPORTED_FILTERS, - enum of supported filters) -inits="$(for i in $filters; do printf 'extern void '$i'_filter_init(struct filter *f); '; done)" -AC_DEFINE_UNQUOTED(DECLARE_FILTER_INITS, $inits, init functions of the supported filters) -array="$(for i in $filters; do printf '{.name = "'$i'", .init = '$i'_filter_init},'; done)" -AC_DEFINE_UNQUOTED(FILTER_ARRAY, $array, array of supported filters) ########################################################################## recv -recv_cmdline_objs=" - recv - http_recv - dccp_recv - udp_recv - afh_recv -" - recv_errlist_objs=" http_recv recv_common @@ -772,7 +629,6 @@ recv_errlist_objs=" fd sched stdout - ggo udp_recv buffer_tree afh_recv @@ -788,15 +644,13 @@ NEED_SPEEX_OBJECTS && recv_errlist_objs="$recv_errlist_objs spx_afh spx_common" NEED_OPUS_OBJECTS && recv_errlist_objs="$recv_errlist_objs opus_afh opus_common" NEED_FLAC_OBJECTS && recv_errlist_objs="$recv_errlist_objs flac_afh" -if test $HAVE_FAAD = yes -a $HAVE_MP4V2 = yes; then - recv_errlist_objs="$recv_errlist_objs aac_afh aac_common" +if test $HAVE_FAAD = yes; then + recv_errlist_objs="$recv_errlist_objs aac_afh" fi -recv_objs="add_cmdline($recv_cmdline_objs) $recv_errlist_objs" -AC_SUBST(receivers, "http dccp udp afh") +recv_objs="$recv_errlist_objs" AC_SUBST(recv_objs, add_dot_o($recv_objs)) ########################################################################### afh audio_format_handlers="mp3 wma" -afh_cmdline_objs="afh" afh_errlist_objs=" afh string @@ -807,7 +661,6 @@ afh_errlist_objs=" wma_afh wma_common version - ggo " NEED_OGG_OBJECTS && afh_errlist_objs="$afh_errlist_objs ogg_afh_common" NEED_VORBIS_OBJECTS && { @@ -826,12 +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 @@ -839,7 +692,6 @@ play_errlist_objs=" play fd sched - ggo buffer_tree time string @@ -868,22 +720,6 @@ play_errlist_objs=" 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" - play_cmdline_objs="$play_cmdline_objs osx_write" -fi NEED_OGG_OBJECTS && play_errlist_objs="$play_errlist_objs ogg_afh_common" NEED_VORBIS_OBJECTS && { play_errlist_objs="$play_errlist_objs oggdec_filter ogg_afh" @@ -901,45 +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 @@ -950,59 +771,28 @@ write_errlist_objs=" sched stdin buffer_tree - ggo check_wav version " -writers="file" -default_writer="FILE_WRITE" -if test "$have_core_audio" = "yes"; then - write_errlist_objs="$write_errlist_objs osx_write ipc" - write_cmdline_objs="$write_cmdline_objs osx_write" - writers="$writers osx" - default_writer="OSX_WRITE" -fi NEED_AO_OBJECTS && { write_errlist_objs="$write_errlist_objs ao_write" - write_cmdline_objs="$write_cmdline_objs ao_write" - writers="$writers ao" - default_writer="AO_WRITE" } if test $HAVE_OSS = yes; then write_errlist_objs="$write_errlist_objs oss_write" - write_cmdline_objs="$write_cmdline_objs oss_write" - writers="$writers oss" - default_writer="OSS_WRITE" fi if test $HAVE_ALSA = yes; then write_errlist_objs="$write_errlist_objs alsa_write" - write_cmdline_objs="$write_cmdline_objs alsa_write" - writers="$writers alsa" - default_writer="ALSA_WRITE" fi -AC_SUBST(writers) -write_objs="add_cmdline($write_cmdline_objs) $write_errlist_objs" +write_objs="$write_errlist_objs" AC_SUBST(write_objs, add_dot_o($write_objs)) -enum="$(for i in $writers; do printf "${i}_WRITE, " | tr '[a-z]' '[A-Z]'; done)" -AC_DEFINE_UNQUOTED(WRITER_ENUM, $enum NUM_SUPPORTED_WRITERS, - enum of supported writers) -AC_DEFINE_UNQUOTED(DEFAULT_WRITER, $default_writer, use this writer if none was specified) -names="$(for i in $writers; do printf \"$i\",' ' ; done)" -AC_DEFINE_UNQUOTED(WRITER_NAMES, $names, supported writer names) -inits="$(for i in $writers; do printf 'extern void '$i'_write_init(struct writer *); '; done)" -AC_DEFINE_UNQUOTED(DECLARE_WRITER_INITS, $inits, init functions of the supported writers) -array="$(for i in $writers; do printf '{.init = '$i'_write_init},'; done)" -AC_DEFINE_UNQUOTED(WRITER_ARRAY, $array, array of supported writers) ######################################################################## audioc -audioc_cmdline_objs="audioc" audioc_errlist_objs=" audioc string net fd version - ggo " if test $HAVE_READLINE = yes; then audioc_errlist_objs="$audioc_errlist_objs @@ -1012,7 +802,7 @@ if test $HAVE_READLINE = yes; then time " fi -audioc_objs="add_cmdline($audioc_cmdline_objs) $audioc_errlist_objs" +audioc_objs="$audioc_errlist_objs" AC_SUBST(audioc_objs, add_dot_o($audioc_objs)) ################################################################## status items @@ -1022,7 +812,7 @@ attributes_txt decoder_flags audiod_status play_time attributes_bitmap offset seconds_total stream_start current_time audiod_uptime image_id lyrics_id duration directory lyrics_name image_name path hash channels last_played num_chunks chunk_time amplification artist title year album -comment" +comment max_chunk_size" result= for i in $status_items; do @@ -1052,15 +842,11 @@ unix socket credentials: $have_ucred readline (interactive CLIs): $HAVE_READLINE id3 version 2 support: $HAVE_ID3TAG faad: $HAVE_FAAD -mp4v2: $HAVE_MP4V2 - audio format handlers: $audio_format_handlers -filters: $(echo $filters) -writers: $writers para_server: $build_server para_gui: $build_gui -para_fade: $build_fade +para_mixer: $build_mixer para_client: $build_client para_audiod: $build_audiod ]) diff --git a/crypt.c b/crypt.c index 8116fb6e..f1e42d4a 100644 --- a/crypt.c +++ b/crypt.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -61,41 +60,24 @@ void init_random_seed_or_die(void) srandom(seed); } -static EVP_PKEY *load_key(const char *file, int private) +static int get_private_key(const char *path, RSA **rsa) { - BIO *key; - EVP_PKEY *pkey = NULL; - int ret = check_key_file(file, private); - - if (ret < 0) { - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - return NULL; - } - key = BIO_new(BIO_s_file()); - if (!key) - return NULL; - if (BIO_read_filename(key, file) > 0) { - if (private == LOAD_PRIVATE_KEY) - pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL); - else - pkey = PEM_read_bio_PUBKEY(key, NULL, NULL, NULL); - } - BIO_free(key); - return pkey; -} - -static int get_openssl_key(const char *key_file, RSA **rsa, int private) -{ - EVP_PKEY *key = load_key(key_file, private); - - if (!key) - return (private == LOAD_PRIVATE_KEY)? -E_PRIVATE_KEY - : -E_PUBLIC_KEY; - *rsa = EVP_PKEY_get1_RSA(key); - EVP_PKEY_free(key); - if (!*rsa) - return -E_RSA; - return RSA_size(*rsa); + EVP_PKEY *pkey; + BIO *bio = BIO_new(BIO_s_file()); + + *rsa = NULL; + if (!bio) + return -E_PRIVATE_KEY; + if (BIO_read_filename(bio, path) <= 0) + goto bio_free; + pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + if (!pkey) + goto bio_free; + *rsa = EVP_PKEY_get1_RSA(pkey); + EVP_PKEY_free(pkey); +bio_free: + BIO_free(bio); + return *rsa? RSA_size(*rsa) : -E_PRIVATE_KEY; } /* @@ -160,8 +142,7 @@ fail: return ret; } -int get_asymmetric_key(const char *key_file, int private, - struct asymmetric_key **result) +int get_public_key(const char *key_file, struct asymmetric_key **result) { struct asymmetric_key *key = NULL; void *map = NULL; @@ -171,21 +152,13 @@ int get_asymmetric_key(const char *key_file, int private, char *cp; key = para_malloc(sizeof(*key)); - if (private) { - ret = get_openssl_key(key_file, &key->rsa, LOAD_PRIVATE_KEY); - goto out; - } ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL); if (ret < 0) goto out; ret = is_ssh_rsa_key(map, map_size); if (!ret) { - ret = para_munmap(map, map_size); - map = NULL; - if (ret < 0) - goto out; - ret = get_openssl_key(key_file, &key->rsa, LOAD_PUBLIC_KEY); - goto out; + para_munmap(map, map_size); + return -E_SSH_PARSE; } cp = map + ret; encoded_size = map_size - ret; @@ -215,7 +188,7 @@ out: return ret; } -void free_asymmetric_key(struct asymmetric_key *key) +void free_public_key(struct asymmetric_key *key) { if (!key) return; @@ -229,11 +202,17 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf, struct asymmetric_key *priv; int ret; + ret = check_private_key_file(key_file); + if (ret < 0) + return ret; if (inlen < 0) return -E_RSA; - ret = get_asymmetric_key(key_file, LOAD_PRIVATE_KEY, &priv); - if (ret < 0) + priv = para_malloc(sizeof(*priv)); + ret = get_private_key(key_file, &priv->rsa); + if (ret < 0) { + free(priv); return ret; + } /* * RSA is vulnerable to timing attacks. Generate a random blinding * factor to protect against this kind of attack. @@ -247,7 +226,8 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf, if (ret <= 0) ret = -E_DECRYPT; out: - free_asymmetric_key(priv); + RSA_free(priv->rsa); + free(priv); return ret; } @@ -264,26 +244,16 @@ int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, } struct stream_cipher { - bool use_aes; - union { - RC4_KEY rc4_key; - EVP_CIPHER_CTX *aes; - } context; + EVP_CIPHER_CTX *aes; }; -struct stream_cipher *sc_new(const unsigned char *data, int len, - bool use_aes) +struct stream_cipher *sc_new(const unsigned char *data, int len) { struct stream_cipher *sc = para_malloc(sizeof(*sc)); - sc->use_aes = use_aes; - if (!use_aes) { - RC4_set_key(&sc->context.rc4_key, len, data); - return sc; - } assert(len >= 2 * AES_CRT128_BLOCK_SIZE); - sc->context.aes = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(sc->context.aes, EVP_aes_128_ctr(), NULL, data, + sc->aes = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(sc->aes, EVP_aes_128_ctr(), NULL, data, data + AES_CRT128_BLOCK_SIZE); return sc; } @@ -292,40 +262,10 @@ void sc_free(struct stream_cipher *sc) { if (!sc) return; - EVP_CIPHER_CTX_free(sc->context.aes); + EVP_CIPHER_CTX_free(sc->aes); free(sc); } -/** - * The RC4() implementation of openssl apparently reads and writes data in - * blocks of 8 bytes. So we have to make sure our buffer sizes are a multiple - * of this. - */ -#define RC4_ALIGN 8 - -static void rc4_crypt(RC4_KEY *key, struct iovec *src, struct iovec *dst) -{ - size_t len = src->iov_len, l1, l2; - - assert(len > 0); - assert(len < ((typeof(src->iov_len))-1) / 2); - l1 = ROUND_DOWN(len, RC4_ALIGN); - l2 = ROUND_UP(len, RC4_ALIGN); - - *dst = (typeof(*dst)) { - /* Add one for the terminating zero byte. */ - .iov_base = para_malloc(l2 + 1), - .iov_len = len - }; - RC4(key, l1, src->iov_base, dst->iov_base); - if (len > l1) { - unsigned char remainder[RC4_ALIGN] = ""; - memcpy(remainder, src->iov_base + l1, len - l1); - RC4(key, len - l1, remainder, dst->iov_base + l1); - } - ((char *)dst->iov_base)[len] = '\0'; -} - static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src, struct iovec *dst) { @@ -347,9 +287,7 @@ static void aes_ctr128_crypt(EVP_CIPHER_CTX *ctx, struct iovec *src, void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) { - if (sc->use_aes) - return aes_ctr128_crypt(sc->context.aes, src, dst); - return rc4_crypt(&sc->context.rc4_key, src, dst); + return aes_ctr128_crypt(sc->aes, src, dst); } void hash_function(const char *data, unsigned long len, unsigned char *hash) diff --git a/crypt.h b/crypt.h index 9be7a23e..5e25748c 100644 --- a/crypt.h +++ b/crypt.h @@ -51,22 +51,21 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf, * Read an asymmetric key from a file. * * \param key_file The file containing the key. - * \param private if non-zero, read the private key, otherwise the public key. * \param result The key structure is returned here. * * \return The size of the key on success, negative on errors. */ -int get_asymmetric_key(const char *key_file, int private, - struct asymmetric_key **result); +int get_public_key(const char *key_file, struct asymmetric_key **result); /** - * Deallocate an asymmetric key structure. + * Deallocate a public key. * * \param key Pointer to the key structure to free. * - * This must be called for any key obtained by get_asymmetric_key(). + * This should be called for keys obtained by get_public_key() if the key is no + * longer needed. */ -void free_asymmetric_key(struct asymmetric_key *key); +void free_public_key(struct asymmetric_key *key); /** @@ -119,16 +118,14 @@ struct stream_cipher_context { }; /** - * Allocate and initialize a stream cipher structure. + * Allocate and initialize an aes_ctr128 stream cipher structure. * * \param data The key. * \param len The size of the key. - * \param use_aes True: Use the aes_ctr128 stream cipher, false: Use RC4. * * \return A new stream cipher structure. */ -struct stream_cipher *sc_new(const unsigned char *data, int len, - bool use_aes); +struct stream_cipher *sc_new(const unsigned char *data, int len); /** * Encrypt or decrypt a buffer using a stream cipher. diff --git a/crypt_backend.h b/crypt_backend.h index f9a69d94..fccdd2ef 100644 --- a/crypt_backend.h +++ b/crypt_backend.h @@ -14,4 +14,4 @@ size_t is_ssh_rsa_key(char *data, size_t size); uint32_t read_ssh_u32(const void *vp); int check_ssh_key_header(const unsigned char *blob, int blen); -int check_key_file(const char *file, bool private_key); +int check_private_key_file(const char *file); diff --git a/crypt_common.c b/crypt_common.c index a05572df..1fd8189c 100644 --- a/crypt_common.c +++ b/crypt_common.c @@ -103,25 +103,22 @@ int check_ssh_key_header(const unsigned char *blob, int blen) } /** - * Check existence and permissions of a key file. + * Check existence and permissions of a private key file. * * \param file The path of the key file. - * \param private_key Whether this is a private key. * - * This checks whether the file exists. If it is a private key, we additionally - * check that the permissions are restrictive enough. It is considered an error - * if we own the file and it is readable for others. + * This checks whether the file exists and its permissions are restrictive + * enough. It is considered an error if we own the file and it is readable for + * others. * * \return Standard. */ -int check_key_file(const char *file, bool private_key) +int check_private_key_file(const char *file) { struct stat st; if (stat(file, &st) != 0) return -ERRNO_TO_PARA_ERROR(errno); - if (!private_key) - return 0; if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0) return -E_KEY_PERM; return 1; diff --git a/dccp_recv.c b/dccp_recv.c index 253586e1..318969af 100644 --- a/dccp_recv.c +++ b/dccp_recv.c @@ -18,20 +18,19 @@ #include #include #include +#include +#include "recv_cmd.lsg.h" #include "para.h" #include "error.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "recv.h" #include "string.h" #include "net.h" #include "fd.h" -#include "dccp_recv.cmdline.h" - static void dccp_recv_close(struct receiver_node *rn) { if (rn->fd > 0) @@ -39,25 +38,56 @@ static void dccp_recv_close(struct receiver_node *rn) btr_pool_free(rn->btrp); } +/* Check whether the host supports the requested 'ccid' arguments. */ +static int dccp_recv_ccid_support_check(const struct lls_parse_result *lpr) +{ + uint8_t *ccids; + int i, j, ret, nccids; + unsigned given = RECV_CMD_OPT_GIVEN(DCCP, CCID, lpr); + + ret = dccp_available_ccids(&ccids); + if (ret < 0) + return ret; + nccids = ret; + for (i = 0; i < given; i++) { + uint32_t val = lls_uint32_val(i, + RECV_CMD_OPT_RESULT(DCCP, CCID, lpr)); + for (j = 0; j < nccids && ccids[j] != val; j++) + ; + if (j == nccids) { + PARA_ERROR_LOG("'CCID-%u' not supported on this host\n", + val); + return -ERRNO_TO_PARA_ERROR(EINVAL); + } + } + return 1; +} + static int dccp_recv_open(struct receiver_node *rn) { - struct dccp_recv_args_info *conf = rn->conf; + struct lls_parse_result *lpr = rn->lpr; struct flowopts *fo = NULL; uint8_t *ccids = NULL; int fd, ret, i; + const struct lls_opt_result *r_c = RECV_CMD_OPT_RESULT(DCCP, CCID, lpr); + const char *host = RECV_CMD_OPT_STRING_VAL(DCCP, HOST, lpr); + uint32_t port = RECV_CMD_OPT_UINT32_VAL(DCCP, PORT, lpr); + unsigned given; + ret = dccp_recv_ccid_support_check(lpr); + if (ret < 0) + return ret; /* Copy CCID preference list (u8 array required) */ - if (conf->ccid_given) { - ccids = para_malloc(conf->ccid_given); - fo = flowopt_new(); - - for (i = 0; i < conf->ccid_given; i++) - ccids[i] = conf->ccid_arg[i]; - + given = lls_opt_given(r_c); + if (given) { + ccids = para_malloc(given); + fo = flowopt_new(); + for (i = 0; i < given; i++) + ccids[i] = lls_int32_val(i, r_c); OPT_ADD(fo, SOL_DCCP, DCCP_SOCKOPT_CCID, ccids, i); } - fd = makesock(IPPROTO_DCCP, 0, conf->host_arg, conf->port_arg, fo); + fd = makesock(IPPROTO_DCCP, 0, host, port, fo); flowopt_cleanup(fo); free(ccids); if (fd < 0) @@ -83,42 +113,6 @@ err: return ret; } -/** - * Check whether the host supports the requested 'ccid' arguments. - * \param conf DCCP receiver arguments. - * \return True if all CCIDs requested in \a conf are supported. - */ -static bool dccp_recv_ccid_support_check(struct dccp_recv_args_info *conf) -{ - uint8_t *ccids; - int i, j, nccids; - - nccids = dccp_available_ccids(&ccids); - if (nccids <= 0) - return false; - - for (i = 0; i < conf->ccid_given; i++) { - for (j = 0; j < nccids && ccids[j] != conf->ccid_arg[i]; j++) - ; - if (j == nccids) { - PARA_ERROR_LOG("'CCID-%d' not supported on this host.\n", - conf->ccid_arg[i]); - return false; - } - } - return true; -} - -static void *dccp_recv_parse_config(int argc, char **argv) -{ - struct dccp_recv_args_info *tmp = para_calloc(sizeof(*tmp)); - - dccp_recv_cmdline_parser(argc, argv, tmp); - if (!dccp_recv_ccid_support_check(tmp)) - exit(EXIT_FAILURE); - return tmp; -} - static void dccp_recv_pre_select(struct sched *s, void *context) { struct receiver_node *rn = context; @@ -161,30 +155,9 @@ out: return ret; } -static void dccp_recv_free_config(void *conf) -{ - dccp_recv_cmdline_parser_free(conf); - free(conf); -} - -/** - * The init function of the dccp receiver. - * - * \param r Pointer to the receiver struct to initialize. - * - * Initialize all function pointers of \a r. - */ -void dccp_recv_init(struct receiver *r) -{ - struct dccp_recv_args_info dummy; - - dccp_recv_cmdline_parser_init(&dummy); - r->open = dccp_recv_open; - r->close = dccp_recv_close; - r->pre_select = dccp_recv_pre_select; - r->post_select = dccp_recv_post_select; - r->parse_config = dccp_recv_parse_config; - r->free_config = dccp_recv_free_config; - r->help = (struct ggo_help)DEFINE_GGO_HELP(dccp_recv); - dccp_recv_cmdline_parser_free(&dummy); -} +const struct receiver lsg_recv_cmd_com_dccp_user_data = { + .open = dccp_recv_open, + .close = dccp_recv_close, + .pre_select = dccp_recv_pre_select, + .post_select = dccp_recv_post_select, +}; diff --git a/dccp_send.c b/dccp_send.c index 92dd9333..61d42126 100644 --- a/dccp_send.c +++ b/dccp_send.c @@ -18,7 +18,9 @@ #include #include #include +#include +#include "server.lsg.h" #include "para.h" #include "error.h" #include "string.h" @@ -32,7 +34,6 @@ #include "fd.h" #include "close_on_fork.h" #include "chunk_queue.h" -#include "server.cmdline.h" #include "acl.h" static struct sender_status dccp_sender_status, *dss = &dccp_sender_status; @@ -91,6 +92,7 @@ static int dccp_init_fec(struct sender_client *sc) { int mps, ret; socklen_t ml = sizeof(mps); + uint32_t mss; /* max slize size */ /* If call fails, return some sensible minimum value */ ret = getsockopt(sc->fd, SOL_DCCP, DCCP_SOCKOPT_GET_CUR_MPS, &mps, &ml); @@ -100,8 +102,9 @@ static int dccp_init_fec(struct sender_client *sc) } PARA_INFO_LOG("current MPS = %d bytes\n", mps); assert(mps > 0); - if (conf.dccp_max_slice_size_arg > 0) - mps = PARA_MIN(mps, conf.dccp_max_slice_size_arg); + mss = OPT_UINT32_VAL(DCCP_MAX_SLICE_SIZE); + if (mss > 0 && mss <= INT_MAX) + mps = PARA_MIN(mps, (int)mss); return mps; } @@ -118,6 +121,7 @@ static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds) struct sender_client *sc; struct dccp_fec_client *dfc; int tx_ccid; + uint32_t k, n; sc = accept_sender_client(dss, rfds); if (!sc) @@ -143,8 +147,16 @@ static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds) } dfc = para_calloc(sizeof(*dfc)); sc->private_data = dfc; - dfc->fcp.data_slices_per_group = conf.dccp_data_slices_per_group_arg; - dfc->fcp.slices_per_group = conf.dccp_slices_per_group_arg; + k = OPT_UINT32_VAL(DCCP_DATA_SLICES_PER_GROUP); + n = OPT_UINT32_VAL(DCCP_SLICES_PER_GROUP); + if (k == 0 || n == 0 || k >= n) { + PARA_WARNING_LOG("invalid FEC parameters, using defaults\n"); + dfc->fcp.data_slices_per_group = 3; + dfc->fcp.slices_per_group = 4; + } else { + dfc->fcp.data_slices_per_group = k; + dfc->fcp.slices_per_group = n; + } dfc->fcp.init_fec = dccp_init_fec; dfc->fcp.send_fec = dccp_send_fec; dfc->fcp.need_periodic_header = false; @@ -216,7 +228,7 @@ static char *dccp_status(void) */ void dccp_send_init(struct sender *s) { - int ret, k, n; + int ret; s->status = dccp_status; s->send = NULL; @@ -232,18 +244,9 @@ void dccp_send_init(struct sender *s) s->client_cmds[SENDER_add] = NULL; s->client_cmds[SENDER_delete] = NULL; - k = conf.dccp_data_slices_per_group_arg; - n = conf.dccp_slices_per_group_arg; - - if (k <= 0 || n <= 0 || k >= n) { - PARA_WARNING_LOG("invalid FEC parameters, using defaults\n"); - conf.dccp_data_slices_per_group_arg = 3; - conf.dccp_slices_per_group_arg = 4; - } - - init_sender_status(dss, conf.dccp_access_arg, conf.dccp_access_given, - conf.dccp_port_arg, conf.dccp_max_clients_arg, - conf.dccp_default_deny_given); + init_sender_status(dss, OPT_RESULT(DCCP_ACCESS), + OPT_UINT32_VAL(DCCP_PORT), OPT_UINT32_VAL(DCCP_MAX_CLIENTS), + OPT_GIVEN(DCCP_DEFAULT_DENY)); ret = generic_com_on(dss, IPPROTO_DCCP); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); diff --git a/error.h b/error.h index 97d67817..407859ca 100644 --- a/error.h +++ b/error.h @@ -9,11 +9,9 @@ /** Codes and messages. */ #define PARA_ERRORS \ PARA_ERROR(SUCCESS, "success"), \ - PARA_ERROR(AAC_AFH_INIT, "failed to init aac decoder"), \ PARA_ERROR(AACDEC_INIT, "failed to init aac decoder"), \ PARA_ERROR(AAC_DECODE, "aac decode error"), \ PARA_ERROR(ACL_PERM, "access denied by acl"), \ - PARA_ERROR(ADD_CALLBACK, "can not add callback"), \ PARA_ERROR(ADDRESS_LOOKUP, "can not resolve requested address"),\ PARA_ERROR(AFH_RECV_BAD_FILENAME, "no file name given"), \ PARA_ERROR(AFS_SHORT_READ, "short read from afs socket"), \ @@ -57,15 +55,11 @@ 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"), \ @@ -89,13 +83,11 @@ 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"), \ @@ -136,7 +128,6 @@ 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"), \ @@ -150,24 +141,26 @@ PARA_ERROR(MP3DEC_CORRUPT, "too many corrupt frames"), \ PARA_ERROR(MP3DEC_EOF, "mp3dec: end of file"), \ PARA_ERROR(MP3_INFO, "could not read mp3 info"), \ - PARA_ERROR(MP4ASC, "audio spec config error"), \ - PARA_ERROR(MP4V2, "mp4v2 library error"), \ - PARA_ERROR(MPI_PRINT, "could not convert multi-precision integer"), \ + PARA_ERROR(MP4FF_BAD_CHANNEL_COUNT, "mp4ff: invalid number of channels"), \ + PARA_ERROR(MP4FF_BAD_SAMPLE, "mp4ff: invalid sample number"), \ + PARA_ERROR(MP4FF_BAD_SAMPLERATE, "mp4ff: invalid sample rate"), \ + PARA_ERROR(MP4FF_BAD_SAMPLE_COUNT, "mp4ff: invalid number of samples"), \ + PARA_ERROR(MP4FF_META_READ, "mp4ff: could not read mp4 metadata"), \ + PARA_ERROR(MP4FF_META_WRITE, "mp4ff: could not update mp4 metadata"), \ + PARA_ERROR(MP4FF_OPEN, "mp4ff: open failed"), \ + PARA_ERROR(MP4FF_TRACK, "mp4ff: no audio track"), \ PARA_ERROR(MPI_SCAN, "could not scan multi-precision integer"), \ PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \ PARA_ERROR(NO_AFHI, "audio format handler info required"), \ PARA_ERROR(NO_ATTRIBUTES, "no attributes defined yet"), \ PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \ - PARA_ERROR(NO_CONFIG, "config file not found"), \ PARA_ERROR(NOFD, "did not receive open fd from afs"), \ - PARA_ERROR(NO_FILTERS, "at least one filter must be given"), \ PARA_ERROR(NO_MATCH, "no matches"), \ PARA_ERROR(NO_MOOD, "no mood available"), \ PARA_ERROR(NO_MORE_SLOTS, "no more empty slots"), \ PARA_ERROR(NOT_PLAYING, "not playing"), \ PARA_ERROR(NO_VALID_FILES, "no valid file found in playlist"), \ PARA_ERROR(NO_WMA, "asf/wma format not recognized"), \ - PARA_ERROR(OEAP, "error during oeap (un)padding"), \ PARA_ERROR(OGGDEC_BADHEADER, "invalid vorbis bitstream header"), \ PARA_ERROR(OGGDEC_BADLINK, "invalid stream section or requested link corrupt"), \ PARA_ERROR(OGGDEC_FAULT, "bug or heap/stack corruption"), \ @@ -178,7 +171,6 @@ 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"), \ @@ -187,18 +179,16 @@ 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"), \ @@ -227,14 +217,11 @@ 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?)"), \ @@ -244,8 +231,6 @@ 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"), \ @@ -288,19 +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). * @@ -315,6 +309,10 @@ _static_inline_ const char *para_strerror(int num) assert(weak_osl_strerror); return weak_osl_strerror(num & ~(1U << OSL_ERROR_BIT)); } + if (IS_LLS_ERROR(num)) { + assert(weak_lls_strerror); + return weak_lls_strerror(num & ~(1U << LLS_ERROR_BIT)); + } if (IS_SYSTEM_ERROR(num)) return strerror(num & ~(1U << SYSTEM_ERROR_BIT)); return para_errlist[num]; @@ -337,3 +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 --git a/fade.c b/fade.c deleted file mode 100644 index 67dc4d54..00000000 --- a/fade.c +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (C) 1998 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file fade.c A volume fader and alarm clock for OSS. */ - -#include - -#include "fade.cmdline.h" -#include "para.h" -#include "fd.h" -#include "string.h" -#include "mix.h" -#include "error.h" -#include "ggo.h" -#include "version.h" - -/** Array of error strings. */ -DEFINE_PARA_ERRLIST; - -static struct fade_args_info conf; - -enum mixer_id {MIXER_ENUM}; -static char *mixer_name[] = {MIXER_NAMES}; -DECLARE_MIXER_INITS; -static struct mixer supported_mixer[] = {MIXER_ARRAY}; -#define FOR_EACH_MIXER(i) for ((i) = 0; (i) < NUM_SUPPORTED_MIXERS; (i)++) - -static int loglevel; -static __printf_2_3 void date_log(int ll, const char *fmt, ...) -{ - va_list argp; - time_t t1; - struct tm *tm; - - if (ll < loglevel) - return; - time(&t1); - tm = localtime(&t1); - fprintf(stderr, "%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec); - va_start(argp, fmt); - vprintf(fmt, argp); - va_end(argp); -} -__printf_2_3 void (*para_log)(int, const char*, ...) = date_log; - -static int set_channel(struct mixer *m, struct mixer_handle *h, const char *channel) -{ - - PARA_NOTICE_LOG("using %s mixer channel\n", channel? - channel : "default"); - return m->set_channel(h, channel); -} - -/* Fade to new volume in fade_time seconds. */ -static int fade(struct mixer *m, struct mixer_handle *h, int new_vol, int fade_time) -{ - int vol, diff, incr, ret; - unsigned secs; - struct timespec ts; - unsigned long long tmp, tmp2; /* Careful with that axe, Eugene! */ - - if (fade_time <= 0) - return m->set(h, new_vol); - secs = fade_time; - ret = m->get(h); - if (ret < 0) - goto out; - vol = ret; - PARA_NOTICE_LOG("fading %s from %d to %d in %u seconds\n", - conf.mixer_channel_arg, vol, new_vol, secs); - diff = new_vol - vol; - if (!diff) { - sleep(secs); - goto out; - } - incr = diff > 0? 1: -1; - diff = diff > 0? diff: -diff; - tmp = secs * 1000 / diff; - tmp2 = tmp % 1000; - while ((new_vol - vol) * incr > 0) { - ts.tv_nsec = tmp2 * 1000000; /* really nec ?*/ - ts.tv_sec = tmp / 1000; /* really nec ?*/ - //printf("ts.tv_sec: %i\n", ts.tv_nsec); - vol += incr; - ret = m->set(h, vol); - if (ret < 0) - goto out; - //printf("vol = %i\n", vol); - nanosleep(&ts, NULL); - } -out: - return ret; -} - -static void client_cmd(const char *cmd) -{ - int ret, status, fds[3] = {0, 0, 0}; - pid_t pid; - char *cmdline = make_message(BINDIR "/para_client %s", cmd); - - PARA_NOTICE_LOG("%s\n", cmdline); - ret = para_exec_cmdline_pid(&pid, cmdline, fds); - free(cmdline); - if (ret < 0) { - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - goto fail; - } - do - pid = waitpid(pid, &status, 0); - while (pid == -1 && errno == EINTR); - if (pid < 0) { - PARA_ERROR_LOG("%s\n", strerror(errno)); - goto fail; - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - goto fail; - return; -fail: - PARA_EMERG_LOG("command \"%s\" failed\n", cmd); - exit(EXIT_FAILURE); -} - -static void change_afs_mode(char *afs_mode) -{ - char *cmd; - - client_cmd("stop"); - if (!afs_mode) - return; - cmd = make_message("select %s", afs_mode); - client_cmd(cmd); - free(cmd); -} - -static int set_initial_volume(struct mixer *m, struct mixer_handle *h) -{ - int i, ret; - - for (i = 0; i < conf.ivol_given; i++) { - char *p, *ch, *arg = para_strdup(conf.ivol_arg[i]); - int32_t iv; - p = strchr(arg, ':'); - if (p) { - *p = '\0'; - p++; - ch = arg; - } else { - p = arg; - ch = NULL; - } - ret = para_atoi32(p, &iv); - if (ret < 0) { - free(arg); - return ret; - } - ret = set_channel(m, h, ch); - if (!ch) - ch = "default"; - if (ret < 0) { - PARA_WARNING_LOG("ignoring channel %s\n", ch); - ret = 0; - } else { - PARA_INFO_LOG("initial volume %s: %d\n", ch, iv); - ret = m->set(h, iv); - } - free(arg); - if (ret < 0) - return ret; - } - return 1; -} - -static int sweet_dreams(struct mixer *m, struct mixer_handle *h) -{ - time_t t1, wake_time_epoch; - unsigned int delay; - struct tm *tm; - int ret, min = conf.wake_min_arg; - char *fo_mood = conf.fo_mood_arg; - char *fi_mood = conf.fi_mood_arg; - char *sleep_mood = conf.sleep_mood_arg; - int fit = conf.fi_time_arg; - int fot = conf.fo_time_arg; - int fiv = conf.fi_vol_arg; - int fov = conf.fo_vol_arg; - - /* calculate wake time */ - time(&t1); - if (conf.wake_hour_given) { - int hour = conf.wake_hour_arg; - tm = localtime(&t1); - if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) { - t1 += 86400; /* wake time is tomorrow */ - tm = localtime(&t1); - } - tm->tm_hour = hour; - tm->tm_min = min; - tm->tm_sec = 0; - } else { - t1 += 9 * 60 * 60; /* nine hours from now */ - PARA_INFO_LOG("default wake time: %lu\n", (long unsigned)t1); - tm = localtime(&t1); - } - wake_time_epoch = mktime(tm); - PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min); - client_cmd("stop"); - sleep(1); - if (fot) { - ret = set_initial_volume(m, h); - if (ret < 0) - return ret; - change_afs_mode(fo_mood); - client_cmd("play"); - ret = set_channel(m, h, conf.mixer_channel_arg); - if (ret < 0) - return ret; - ret = fade(m, h, fov, fot); - if (ret < 0) - return ret; - } else { - ret = m->set(h, fov); - if (ret < 0) - return ret; - } - if (conf.sleep_mood_given) { - change_afs_mode(sleep_mood); - client_cmd("play"); - } else - client_cmd("stop"); - if (!fit) - return 1; - change_afs_mode(fi_mood); - for (;;) { - time(&t1); - if (wake_time_epoch <= t1 + fit) - break; - delay = wake_time_epoch - t1 - fit; - PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n", - delay, delay / 3600, - (delay % 3600) / 60); - sleep(delay); - } - client_cmd("play"); - ret = fade(m, h, fiv, fit); - PARA_INFO_LOG("fade complete, returning\n"); - return ret; -} - -static int snooze(struct mixer *m, struct mixer_handle *h) -{ - int ret, val; - - if (conf.so_time_arg <= 0) - return 1; - ret = m->get(h); - if (ret < 0) - return ret; - val = ret; - if (val < conf.so_vol_arg) - ret = m->set(h, conf.so_vol_arg); - else - ret = fade(m, h, conf.so_vol_arg, conf.so_time_arg); - if (ret < 0) - return ret; - client_cmd("pause"); - PARA_NOTICE_LOG("%d seconds snooze time...\n", conf.snooze_time_arg); - sleep(conf.snooze_time_arg); - client_cmd("play"); - return fade(m, h, conf.si_vol_arg, conf.si_time_arg); -} - -static int configfile_exists(void) -{ - static char *config_file; - - if (!conf.config_file_given) { - char *home = para_homedir(); - free(config_file); - config_file = make_message("%s/.paraslash/fade.conf", home); - free(home); - conf.config_file_arg = config_file; - } - return file_exists(conf.config_file_arg); -} - -static void init_mixers(void) -{ - int i; - - FOR_EACH_MIXER(i) { - struct mixer *m = &supported_mixer[i]; - PARA_DEBUG_LOG("initializing mixer API #%d (%s)\n", - i, mixer_name[i]); - m->init(m); - } -} - -static int set_val(struct mixer *m, struct mixer_handle *h) -{ - return m->set(h, conf.val_arg); -} - -static struct mixer *get_mixer_or_die(void) -{ - int i; - - if (!conf.mixer_api_given) - i = DEFAULT_MIXER; - else - FOR_EACH_MIXER(i) - if (!strcmp(mixer_name[i], conf.mixer_api_arg)) - break; - if (i < NUM_SUPPORTED_MIXERS) { - PARA_NOTICE_LOG("using %s mixer API\n", mixer_name[i]); - return supported_mixer + i; - } - printf("available mixer APIs: "); - FOR_EACH_MIXER(i) { - int d = (i == DEFAULT_MIXER); - printf("%s%s%s ", d? "[" : "", mixer_name[i], d? "]" : ""); - } - printf("\n"); - exit(EXIT_FAILURE); -} - -__noreturn static void print_help_and_die(void) -{ - struct ggo_help h = DEFINE_GGO_HELP(fade); - bool d = conf.detailed_help_given; - - ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS); - exit(0); -} - -/** - * The main function of para_fade. - * - * The executable is linked with the alsa or the oss mixer API, or both. It has - * a custom log function which prefixes log messages with the current date. - * - * \param argc Argument counter. - * \param argv Argument vector. - * - * \return EXIT_SUCCESS or EXIT_FAILURE. - */ -int main(int argc, char *argv[]) -{ - int ret; - struct mixer *m; - struct mixer_handle *h = NULL; - - fade_cmdline_parser(argc, argv, &conf); - loglevel = get_loglevel_by_name(conf.loglevel_arg); - version_handle_flag("fade", conf.version_given); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); - ret = configfile_exists(); - if (!ret && conf.config_file_given) { - PARA_EMERG_LOG("can not read config file %s\n", - conf.config_file_arg); - exit(EXIT_FAILURE); - } - if (ret) { - struct fade_cmdline_parser_params params = { - .override = 0, - .initialize = 0, - .check_required = 0, - .check_ambiguity = 0, - .print_errors = 1 - }; - fade_cmdline_parser_config_file(conf.config_file_arg, - &conf, ¶ms); - loglevel = get_loglevel_by_name(conf.loglevel_arg); - } - init_mixers(); - m = get_mixer_or_die(); - ret = m->open(conf.mixer_device_arg, &h); - if (ret < 0) - goto out; - ret = set_channel(m, h, conf.mixer_channel_arg); - if (ret == -E_BAD_CHANNEL) { - char *channels = m->get_channels(h); - printf("Available channels: %s\n", channels); - free(channels); - } - if (ret < 0) - goto out; - switch (conf.mode_arg) { - case mode_arg_fade: - ret = fade(m, h, conf.fade_vol_arg, conf.fade_time_arg); - break; - case mode_arg_snooze: - ret = snooze(m, h); - break; - case mode_arg_set: - ret = set_val(m, h); - break; - default: /* sleep mode */ - ret = sweet_dreams(m, h); - break; - } -out: - m->close(&h); - if (ret < 0) - PARA_EMERG_LOG("%s\n", para_strerror(-ret)); - return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/fecdec_filter.c b/fecdec_filter.c index 1c3a3784..1b95ea4c 100644 --- a/fecdec_filter.c +++ b/fecdec_filter.c @@ -12,7 +12,6 @@ #include "error.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "string.h" @@ -481,15 +480,9 @@ static void fecdec_open(struct filter_node *fn) fn->min_iqs = FEC_HEADER_SIZE; } -/** - * The init function of the fecdec filter. - * - * \param f Struct to initialize. - */ -void fecdec_filter_init(struct filter *f) -{ - f->close = fecdec_close; - f->open = fecdec_open; - f->pre_select = generic_filter_pre_select; - f->post_select = fecdec_post_select; -} +const struct filter lsg_filter_cmd_com_fecdec_user_data = { + .open = fecdec_open, + .pre_select = generic_filter_pre_select, + .post_select = fecdec_post_select, + .close = fecdec_close, +}; diff --git a/file_write.c b/file_write.c index 5e66607e..9837e810 100644 --- a/file_write.c +++ b/file_write.c @@ -8,17 +8,16 @@ #include #include +#include +#include "write_cmd.lsg.h" #include "para.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "write.h" -#include "write_common.h" #include "string.h" #include "fd.h" -#include "file_write.cmdline.h" #include "error.h" /** Data specific to the file writer. */ @@ -47,31 +46,31 @@ __must_check __malloc static char *random_filename(void) static int prepare_output_file(struct writer_node *wn) { - struct file_write_args_info *conf = wn->conf; - char *filename; - int ret; - struct private_file_write_data *pfwd = para_calloc(sizeof(*pfwd)); - - if (conf->filename_given) - filename = conf->filename_arg; - else - filename = random_filename(); - ret = para_open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); - if (!conf->filename_given) - free(filename); - if (ret < 0) - goto out; - pfwd->fd = ret; - ret = mark_fd_blocking(pfwd->fd); + const unsigned flags = O_WRONLY | O_CREAT, mode = S_IRUSR | S_IWUSR; + int ret, fd; + struct private_file_write_data *pfwd; + + + if (WRITE_CMD_OPT_GIVEN(FILE, FILENAME, wn->lpr)) { + const char *path = WRITE_CMD_OPT_STRING_VAL(FILE, FILENAME, + wn->lpr); + ret = para_open(path, flags, mode); + } else { + char *path = random_filename(); + ret = para_open(path, flags, mode); + free(path); + } if (ret < 0) - goto out_close; - wn->private_data = pfwd; + return ret; + fd = ret; + ret = mark_fd_blocking(fd); + if (ret < 0) { + close(fd); + return ret; + } + pfwd = wn->private_data = para_calloc(sizeof(*pfwd)); + pfwd->fd = fd; return 1; -out_close: - close(pfwd->fd); -out: - free(pfwd); - return ret; } static void file_write_pre_select(struct sched *s, void *context) @@ -131,31 +130,9 @@ out: return ret; } -__malloc static void *file_write_parse_config_or_die(int argc, char **argv) -{ - struct file_write_args_info *conf = para_calloc(sizeof(*conf)); - - /* exits on errors */ - file_write_cmdline_parser(argc, argv, conf); - return conf; -} - -static void file_write_free_config(void *conf) -{ - file_write_cmdline_parser_free(conf); -} - /** the init function of the file writer */ -void file_write_init(struct writer *w) -{ - struct file_write_args_info dummy; - - file_write_cmdline_parser_init(&dummy); - w->pre_select = file_write_pre_select; - w->post_select = file_write_post_select; - w->parse_config_or_die = file_write_parse_config_or_die; - w->free_config = file_write_free_config; - w->close = file_write_close; - w->help = (struct ggo_help)DEFINE_GGO_HELP(file_write); - file_write_cmdline_parser_free(&dummy); -} +struct writer lsg_write_cmd_com_file_user_data = { + .pre_select = file_write_pre_select, + .post_select = file_write_post_select, + .close = file_write_close, +}; diff --git a/filter.c b/filter.c index 81901896..d5dac675 100644 --- a/filter.c +++ b/filter.c @@ -7,22 +7,31 @@ /** \file filter.c The stand-alone filter program. */ #include +#include +#include "filter.lsg.h" +#include "filter_cmd.lsg.h" #include "para.h" -#include "filter.cmdline.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "string.h" #include "stdin.h" #include "stdout.h" #include "error.h" +#include "fd.h" #include "version.h" /** Array of error strings. */ DEFINE_PARA_ERRLIST; +static struct lls_parse_result *lpr; /* command line options */ + +#define CMD_PTR (lls_cmd(0, filter_suite)) +#define OPT_RESULT(_name) \ + (lls_opt_result(LSG_FILTER_PARA_FILTER_OPT_ ## _name, lpr)) +#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name))) +#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name))) /** The list of all status items used by para_{server,audiod,gui}. */ const char *status_item_list[] = {STATUS_ITEM_ARRAY}; @@ -45,48 +54,75 @@ static struct stdout_task stdout_task_struct; /** Pointer to the stdout task. */ static struct stdout_task *sot = &stdout_task_struct; -/** Gengetopt struct that holds the command line args. */ -static struct filter_args_info conf; - static int loglevel; INIT_STDERR_LOGGING(loglevel); -__noreturn static void print_help_and_die(void) +static void handle_help_flag(void) { - struct ggo_help h = DEFINE_GGO_HELP(filter); - bool d = conf.detailed_help_given; - - ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS); - print_filter_helps(d? GPH_MODULE_FLAGS_DETAILED : GPH_MODULE_FLAGS); + char *help; + + if (OPT_GIVEN(DETAILED_HELP)) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP)) + help = lls_short_help(CMD_PTR); + else + return; + printf("%s\n", help); + free(help); + print_filter_helps(OPT_GIVEN(DETAILED_HELP)); exit(EXIT_SUCCESS); } static int parse_config(void) { - static char *cf; /* config file */ + char *home, *cf; /* config file */ struct stat statbuf; + int ret; - version_handle_flag("filter", conf.version_given); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); - if (!cf) { - char *home = para_homedir(); - cf = make_message("%s/.paraslash/filter.conf", home); - free(home); - } + version_handle_flag("filter", OPT_GIVEN(VERSION)); + handle_help_flag(); + home = para_homedir(); + cf = make_message("%s/.paraslash/filter.conf", home); + free(home); if (!stat(cf, &statbuf)) { - struct filter_cmdline_parser_params params = { - .override = 0, - .initialize = 0, - .check_required = 0, - .check_ambiguity = 0, - .print_errors = 1 - }; - filter_cmdline_parser_config_file(cf, &conf, ¶ms); - loglevel = get_loglevel_by_name(conf.loglevel_arg); + void *map; + size_t sz; + int cf_argc; + char **cf_argv, *errctx; + struct lls_parse_result *cf_lpr, *merged_lpr; + + ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL); + if (ret != -E_EMPTY) { + if (ret < 0) + return ret; + ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, + &errctx)); + para_munmap(map, sz); + if (ret < 0) { + PARA_ERROR_LOG("syntax error in %s\n", cf); + return ret; + } + cf_argc = ret; + ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, + &errctx)); + lls_free_argv(cf_argv); + if (ret < 0) { + PARA_ERROR_LOG("parse error in %s\n", cf); + return ret; + } + ret = lls(lls_merge(lpr, cf_lpr, CMD_PTR, &merged_lpr, &errctx)); + lls_free_parse_result(cf_lpr, CMD_PTR); + if (ret < 0) + return ret; + lls_free_parse_result(lpr, CMD_PTR); + lpr = merged_lpr; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + } + } + if (!OPT_GIVEN(FILTER)) { + print_filter_list(); + exit(EXIT_SUCCESS); } - if (!conf.filter_given) - return -E_NO_FILTERS; return 1; } @@ -109,36 +145,37 @@ int main(int argc, char *argv[]) const struct filter *f; struct btr_node *parent; struct filter_node **fns; + struct lls_parse_result *filter_lpr; /* per-filter options */ + char *errctx; - filter_cmdline_parser(argc, argv, &conf); /* aborts on errors */ - loglevel = get_loglevel_by_name(conf.loglevel_arg); - filter_init(); - ret = parse_config(); + ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx)); if (ret < 0) goto out; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + ret = parse_config(); + if (ret < 0) + goto free_lpr; sit->btrn = btr_new_node(&(struct btr_node_description) EMBRACE(.name = "stdin")); stdin_task_register(sit, &s); - fns = para_malloc(conf.filter_given * sizeof(*fns)); - for (i = 0, parent = sit->btrn; i < conf.filter_given; i++) { - char *fa = conf.filter_arg[i]; + fns = para_malloc(OPT_GIVEN(FILTER) * sizeof(*fns)); + for (i = 0, parent = sit->btrn; i < OPT_GIVEN(FILTER); i++) { + const char *fa = lls_string_val(i, OPT_RESULT(FILTER)); + const char *name; struct filter_node *fn; struct task_info ti; fn = fns[i] = para_calloc(sizeof(*fn)); - ret = check_filter_arg(fa, &fn->conf); - if (ret < 0) { - free(fn); - goto out_cleanup; - } - fn->filter_num = ret; + fn->filter_num = filter_setup(fa, &fn->conf, &filter_lpr); + name = filter_name(fn->filter_num); + fn->lpr = filter_lpr; + PARA_DEBUG_LOG("filter #%d: %s\n", i, name); f = filter_get(fn->filter_num); - PARA_DEBUG_LOG("filter #%d: %s\n", i, f->name); fn->btrn = btr_new_node(&(struct btr_node_description) - EMBRACE(.name = f->name, .parent = parent, + EMBRACE(.name = name, .parent = parent, .handler = f->execute, .context = fn)); - ti.name = f->name; + ti.name = name; ti.pre_select = f->pre_select; ti.post_select = f->post_select; ti.context = fn; @@ -156,7 +193,6 @@ int main(int argc, char *argv[]) btr_log_tree(sit->btrn, LL_INFO); ret = schedule(&s); sched_shutdown(&s); -out_cleanup: for (i--; i >= 0; i--) { struct filter_node *fn = fns[i]; @@ -164,15 +200,21 @@ out_cleanup: if (f->close) f->close(fn); btr_remove_node(&fn->btrn); - if (f->free_config) - f->free_config(fn->conf); + if (f->teardown) + f->teardown(fn->lpr, fn->conf); free(fn); } free(fns); btr_remove_node(&sit->btrn); btr_remove_node(&sot->btrn); +free_lpr: + lls_free_parse_result(lpr, CMD_PTR); out: - if (ret < 0) - PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + } exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); } diff --git a/filter.h b/filter.h index 0bd54690..4537b220 100644 --- a/filter.h +++ b/filter.h @@ -6,9 +6,6 @@ /** \file filter.h Filter-related structures and exported symbols from filter_common.c. */ -/** The list of supported filters. */ -enum filter_enum {FILTER_ENUM}; - /** * Describes one running instance of a filter. */ @@ -24,6 +21,8 @@ struct filter_node { struct list_head callbacks; /** A pointer to the configuration of this instance. */ void *conf; + /** The parsed command line, merged with options given in the config file. */ + struct lls_parse_result *lpr; /** The buffer tree node. */ struct btr_node *btrn; /** The task corresponding to this filter node. */ @@ -35,26 +34,16 @@ struct filter_node { /** * The structure associated with a paraslash filter. * - * Paraslash filters are "modules" which are used to transform an audio stream. - * struct filter contains pointers to functions that must be supplied by the - * filter code in order to be used by the driving application (currently - * para_audiod and para_filter). + * Paraslash filters are "modules" which transform an audio stream. struct + * filter contains methods which are implemented by each filter. * * Note: As several instances of the same filter may be running at the same * time, all these filter functions must be reentrant; no static non-constant * variables may be used. + * * \sa mp3dec_filter.c, oggdec_filter.c, wav_filter.c, compress_filter.c, filter_node */ struct filter { - /** The name of the filter. */ - const char *name; - /** - * Pointer to the filter init routine. - * - * This function is only called once at startup. It must initialize the - * other non-optional function pointers of this structure. - */ - void (*init)(struct filter *f); /** * Open one instance of this filter. * @@ -73,27 +62,29 @@ struct filter { */ void (*close)(struct filter_node *fn); /** - * A pointer to the filter's command line parser. + * Prepare the filter according to command line options. * - * If this optional function pointer is not NULL, any filter options - * are passed from the main program to this command line parser once at - * application startup. The command line parser should check its - * command line options given by \a argc and \a argv and abort on - * errors. Success must be indicated by a non-negative return value. In - * this case the function should return a pointer to the - * filter-specific configuration data determined by \a argc and \a - * argv. On failure, a negative paraslash error code must be returned. + * In addition to the syntactic checks which are automatically performed + * by the lopsub functions, some filters like to also check the command + * line arguments semantically. Moreover, since applications may open + * the filter many times with the same options, filters need a method + * which allows them to precompute once those parts of the setup which + * depend only on the command line options. + * + * If this function pointer is not NULL, the function is called once at + * startup. The returned pointer value is made available to the ->open + * method via the ->conf pointer of struct filter_node. + * + * Filters are supposed to abort if the setup fails. If the function + * returns, it is assumed to have succeeded. */ - int (*parse_config)(int argc, char **argv, void **config); + void *(*setup)(const struct lls_parse_result *lpr); /** - * Deallocate the memory for the configuration. + * Deallocate precomputed resources. * - * This is called to free whatever ->parse_config() has allocated. + * This should free whatever ->setup() has allocated. */ - void (*free_config)(void *conf); - - /** The help texts for this filter. */ - struct ggo_help help; + void (*teardown)(const struct lls_parse_result *lpr, void *conf); /** * Set scheduler timeout and add file descriptors to fd sets. * @@ -121,9 +112,22 @@ struct filter { btr_command_handler execute; }; -void filter_init(void); -int check_filter_arg(const char *fa, void **conf); -void print_filter_helps(unsigned flags); +void print_filter_helps(bool detailed); +void print_filter_list(void); +int filter_setup(const char *fa, void **conf, struct lls_parse_result **lprp); +#define FILTER_CMD(_num) (lls_cmd(_num, filter_cmd_suite)) +#define FILTER_CMD_OPT(_cmd, _opt) (lls_opt( \ + LSG_FILTER_CMD_ ## _cmd ## _OPT_ ## _opt, \ + FILTER_CMD(LSG_FILTER_CMD_CMD_ ## _cmd))) +#define FILTER_CMD_OPT_RESULT(_cmd, _opt, _lpr) \ + (lls_opt_result(LSG_FILTER_CMD_ ## _cmd ## _OPT_ ## _opt, _lpr)) +#define FILTER_CMD_OPT_GIVEN(_cmd, _opt, _lpr) \ + (lls_opt_given(FILTER_CMD_OPT_RESULT(_cmd, _opt, _lpr))) +#define FILTER_CMD_OPT_UINT32_VAL(_cmd, _opt, _lpr) \ + (lls_uint32_val(0, FILTER_CMD_OPT_RESULT(_cmd, _opt, _lpr))) +#define FILTER_CMD_OPT_STRING_VAL(_cmd, _opt, _lpr) \ + (lls_string_val(0, FILTER_CMD_OPT_RESULT(_cmd, _opt, _lpr))) + void generic_filter_pre_select(struct sched *s, void *context); int decoder_execute(const char *cmd, unsigned sample_rate, unsigned channels, char **result); @@ -139,6 +143,6 @@ static inline void write_int16_host_endian(char *buf, int val) #endif } -DECLARE_FILTER_INITS - +/** Make a filter pointer from the filter number. */ const struct filter *filter_get(int filter_num); +const char *filter_name(int filter_num); diff --git a/filter_common.c b/filter_common.c index e9b97e54..b406951e 100644 --- a/filter_common.c +++ b/filter_common.c @@ -8,144 +8,167 @@ #include #include +#include +#include "filter_cmd.lsg.h" #include "para.h" #include "list.h" #include "sched.h" #include "fd.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "error.h" #include "string.h" -/** Iterate over the array of supported filters. */ -#define FOR_EACH_SUPPORTED_FILTER(j) for (j = 0; j < NUM_SUPPORTED_FILTERS; j++) - -/** The array of supported filters. */ -static struct filter filters[NUM_SUPPORTED_FILTERS] = {FILTER_ARRAY}; +/** Iterate over all filters. */ +#define FOR_EACH_FILTER(j) for (j = 1; lls_cmd(j, filter_cmd_suite); j++) /** * Obtain a reference to a filter structure. * * \param filter_num Between zero and NUM_SUPPORTED_FILTERS, inclusively. * - * \return Pointer to the filter identified by the given filter number. + * \return Pointer to the filter identified by the given filter number, or + * NULL if the filter number is out of range. * - * It is a fatal error if the given number is out of range. In this case - * the function aborts. + * \sa filter_name(). */ const struct filter *filter_get(int filter_num) { - assert(filter_num >= 0); - assert(filter_num < NUM_SUPPORTED_FILTERS); - return filters + filter_num; + if (filter_num < 1 || filter_num > LSG_NUM_FILTER_CMD_SUBCOMMANDS) + return NULL; + return lls_user_data(FILTER_CMD(filter_num)); } -/** - * Call the init function of each supported filter. - * \sa filter::init - */ -void filter_init(void) +static inline bool filter_supported(int filter_num) { - int i; - - FOR_EACH_SUPPORTED_FILTER(i) - filter_get(i)->init((struct filter *)filter_get(i)); + return lls_user_data(FILTER_CMD(filter_num)); } -/* - * If the filter has a command line parser and options is not NULL, run it. - * Returns filter_num on success, negative on errors +/** + * Return the name of a filter, given its number. + * + * \param filter_num See \ref filter_get(). + * + * \return A pointer to a string literal, or NULL if filter_num is out of + * range. The caller must not attempt to call free(3) on the returned pointer. */ -static int parse_filter_args(int filter_num, const char *options, void **conf) +const char *filter_name(int filter_num) { - const struct filter *f = filter_get(filter_num); - int ret, argc; - char **argv; - - if (!f->parse_config) - return strlen(options)? -E_BAD_FILTER_OPTIONS : filter_num; - argc = create_shifted_argv(options, " \t", &argv); - if (argc < 0) - return -E_BAD_FILTER_OPTIONS; - argv[0] = para_strdup(f->name); - ret = f->parse_config(argc, argv, conf); - free_argv(argv); - return ret < 0? ret : filter_num; + if (filter_num < 1 || filter_num > LSG_NUM_FILTER_CMD_SUBCOMMANDS) + return NULL; + return lls_command_name(FILTER_CMD(filter_num)); } /** - * Check the filter command line options. + * Parse a filter command line and call the corresponding ->setup method. * * \param fa The filter argument. - * \param conf Points to the filter configuration upon successful return. + * \param conf Points to filter-specific setup upon successful return. + * \param lprp Parsed command line options are returned here. * * Check if the given filter argument starts with the name of a supported * filter, optionally followed by options for this filter. If yes, call the - * command line parser of that filter. - * - * \return On success, the number of the filter is returned and \a conf - * is initialized to point to the filter configuration determined by \a fa. - * On errors, a negative value is returned. + * command line parser of that filter and its ->setup method. * - * Note: If \a fa specifies a filter that has no command line parser success is - * returned, and \a conf is initialized to \p NULL. - * - * \sa filter::parse_config + * \return This function either succeeds or does not return. On success, the + * number of the filter is returned and conf is initialized to point to the + * filter configuration as returned by the filter's ->setup() method, if any. + * Moreover, *lprp is initialized to contain the parsed command line options. + * On errors, the function calls exit(EXIT_FAILURE). */ -int check_filter_arg(const char *fa, void **conf) +int filter_setup(const char *fa, void **conf, struct lls_parse_result **lprp) { - int j; - - *conf = NULL; -// PARA_DEBUG_LOG("arg: %s\n", fa); - FOR_EACH_SUPPORTED_FILTER(j) { - const char *name = filter_get(j)->name; - size_t len = strlen(name); - char c; - if (strlen(fa) < len) - continue; - if (strncmp(name, fa, len)) - continue; - c = fa[len]; - if (c && c != ' ') - continue; - if (c && !filter_get(j)->parse_config) - return -E_BAD_FILTER_OPTIONS; - return parse_filter_args(j, c? fa + len + 1 : - fa + strlen(fa), conf); + int ret, filter_num, argc; + char *errctx = NULL, **argv; + const struct lls_command *cmd; + const struct filter *f; + + ret = create_argv(fa, " \t\n", &argv); + if (ret < 0) + goto fail; + argc = ret; + ret = lls(lls_lookup_subcmd(argv[0], filter_cmd_suite, &errctx)); + if (ret < 0) + goto free_argv; + filter_num = ret; + cmd = FILTER_CMD(filter_num); + if (!filter_supported(filter_num)) { + ret = -E_UNSUPPORTED_FILTER; + errctx = make_message("bad filter name: %s", + lls_command_name(cmd)); + goto free_argv; } - return -E_UNSUPPORTED_FILTER; + ret = lls(lls_parse(argc, argv, cmd, lprp, &errctx)); + if (ret < 0) + goto free_argv; + f = filter_get(filter_num); + *conf = f->setup? f->setup(*lprp) : NULL; + ret = filter_num; +free_argv: + free_argv(argv); + if (ret >= 0) + return ret; +fail: + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + exit(EXIT_FAILURE); } /** * Print help text of each filter to stdout. * - * \param flags Passed to \ref ggo_print_help(). + * \param detailed Whether to print short or long help. */ -void print_filter_helps(unsigned flags) +void print_filter_helps(bool detailed) { int i, num = 0; - printf_or_die("\nAvailable filters: "); - FOR_EACH_SUPPORTED_FILTER(i) { + printf("\nAvailable filters: "); + FOR_EACH_FILTER(i) { + if (!filter_supported(i)) + continue; if (num > 50) { - printf_or_die("\n "); + printf("\n "); num = 0; } - num += printf_or_die("%s%s", i? " " : "", filter_get(i)->name); + num += printf("%s%s", i? " " : "", filter_name(i)); + } + printf("\n"); + + FOR_EACH_FILTER(i) { + const struct lls_command *cmd = FILTER_CMD(i); + char *help; + + if (!filter_supported(i)) + continue; + help = detailed? lls_long_help(cmd) : lls_short_help(cmd); + if (!help) + continue; + printf("%s\n", help); + free(help); } - printf_or_die("\n"); +} - FOR_EACH_SUPPORTED_FILTER(i) { - struct filter *f = (struct filter *)filter_get(i); +/** + * Print a short summary of all available filters to stdout. + * + * For each supported filter, the filter name and the purpose text is printed + * in a single line. Since no options are shown, the filter list is more + * concise than the text obtained from print_filter_helps(). + */ +void print_filter_list(void) +{ + int i; - if (!f->help.short_help) + printf("Available filters:\n"); + FOR_EACH_FILTER(i) { + const struct lls_command *cmd = FILTER_CMD(i); + if (!filter_supported(i)) continue; - printf_or_die("\nOptions for %s (%s):", f->name, - f->help.purpose); - ggo_print_help(&f->help, flags); + printf("%-9s %s\n", filter_name(i), lls_purpose(cmd)); } } diff --git a/flac_afh.c b/flac_afh.c index 385d4f0c..e2d53802 100644 --- a/flac_afh.c +++ b/flac_afh.c @@ -391,6 +391,7 @@ static int flac_afh_read_chunks(struct private_flac_afh_data *pfad) break; } afhi->chunks_total = c; + set_max_chunk_size(afhi); ret = 1; free_decoder: FLAC__stream_decoder_finish(decoder); diff --git a/flacdec_filter.c b/flacdec_filter.c index bbacb3da..16237acb 100644 --- a/flacdec_filter.c +++ b/flacdec_filter.c @@ -12,7 +12,6 @@ #include "para.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "error.h" @@ -296,18 +295,10 @@ static void flacdec_open(struct filter_node *fn) fn->min_iqs = 0; } -/** - * The init function of the flacdec filter. - * - * \param f Pointer to the filter struct to initialize. - * - * \sa filter::init. - */ -void flacdec_filter_init(struct filter *f) -{ - f->open = flacdec_open; - f->close = flacdec_close; - f->pre_select = flacdec_pre_select; - f->post_select = flacdec_post_select; - f->execute = flacdec_execute; -} +const struct filter lsg_filter_cmd_com_flacdec_user_data = { + .open = flacdec_open, + .close = flacdec_close, + .pre_select = flacdec_pre_select, + .post_select = flacdec_post_select, + .execute = flacdec_execute, +}; diff --git a/gcrypt.c b/gcrypt.c index 7c19aeb0..1fde479e 100644 --- a/gcrypt.c +++ b/gcrypt.c @@ -19,9 +19,6 @@ //#define GCRYPT_DEBUG 1 -static bool libgcrypt_has_oaep; -static const char *rsa_decrypt_sexp; - #ifdef GCRYPT_DEBUG static void dump_buffer(const char *msg, unsigned char *buf, int len) { @@ -63,134 +60,25 @@ void get_random_bytes_or_die(unsigned char *buf, int num) * don't have to initialize any random seed here, but we must initialize the * gcrypt library. This task is performed by gcry_check_version() which can * also check that the gcrypt library version is at least the minimal required - * version. This function also tells us whether we have to use our own OAEP - * padding code. + * version. */ void init_random_seed_or_die(void) { - const char *ver, *req_ver; - - ver = gcry_check_version(NULL); - req_ver = "1.4.0"; - if (!gcry_check_version(req_ver)) { - PARA_EMERG_LOG("fatal: need at least libgcrypt-%s, have: %s\n", - req_ver, ver); - exit(EXIT_FAILURE); - } - req_ver = "1.5.0"; - if (gcry_check_version(req_ver)) { - libgcrypt_has_oaep = true; - rsa_decrypt_sexp = "(enc-val(flags oaep)(rsa(a %m)))"; - } else { - libgcrypt_has_oaep = false; - rsa_decrypt_sexp = "(enc-val(rsa(a %m)))"; - } + const char *req_ver = "1.5.0"; + + if (gcry_check_version(req_ver)) + return; + PARA_EMERG_LOG("fatal: need at least libgcrypt-%s, have: %s\n", + req_ver, gcry_check_version(NULL)); + exit(EXIT_FAILURE); } /** S-expression for the public part of an RSA key. */ #define RSA_PUBKEY_SEXP "(public-key (rsa (n %m) (e %m)))" /** S-expression for a private RSA key. */ #define RSA_PRIVKEY_SEXP "(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))" - -/* rfc 3447, appendix B.2 */ -static void mgf1(unsigned char *seed, size_t seed_len, unsigned result_len, - unsigned char *result) -{ - gcry_error_t gret; - gcry_md_hd_t handle; - size_t n; - unsigned char *md; - unsigned char octet_string[4], *rp = result, *end = rp + result_len; - - assert(result_len / HASH_SIZE < 1ULL << 31); - gret = gcry_md_open(&handle, GCRY_MD_SHA1, 0); - assert(gret == 0); - for (n = 0; rp < end; n++) { - gcry_md_write(handle, seed, seed_len); - octet_string[0] = (unsigned char)((n >> 24) & 255); - octet_string[1] = (unsigned char)((n >> 16) & 255); - octet_string[2] = (unsigned char)((n >> 8)) & 255; - octet_string[3] = (unsigned char)(n & 255); - gcry_md_write(handle, octet_string, 4); - gcry_md_final(handle); - md = gcry_md_read(handle, GCRY_MD_SHA1); - memcpy(rp, md, PARA_MIN(HASH_SIZE, (int)(end - rp))); - rp += HASH_SIZE; - gcry_md_reset(handle); - } - gcry_md_close(handle); -} - -/** The sha1 hash of an empty file. */ -static const unsigned char empty_hash[HASH_SIZE] = - "\xda" "\x39" "\xa3" "\xee" "\x5e" - "\x6b" "\x4b" "\x0d" "\x32" "\x55" - "\xbf" "\xef" "\x95" "\x60" "\x18" - "\x90" "\xaf" "\xd8" "\x07" "\x09"; - -/* rfc3447, section 7.1.1 */ -static void pad_oaep(unsigned char *in, size_t in_len, unsigned char *out, - size_t out_len) -{ - size_t ps_len = out_len - in_len - 2 * HASH_SIZE - 2; - size_t n, mask_len = out_len - HASH_SIZE - 1; - unsigned char *seed = out + 1, *db = seed + HASH_SIZE, - *ps = db + HASH_SIZE, *one = ps + ps_len; - unsigned char *db_mask, seed_mask[HASH_SIZE]; - - assert(in_len <= out_len - 2 - 2 * HASH_SIZE); - assert(out_len > 2 * HASH_SIZE + 2); - PARA_DEBUG_LOG("padding %zu byte input -> %zu byte output\n", - in_len, out_len); - dump_buffer("unpadded buffer", in, in_len); - - out[0] = '\0'; - get_random_bytes_or_die(seed, HASH_SIZE); - memcpy(db, empty_hash, HASH_SIZE); - memset(ps, 0, ps_len); - *one = 0x01; - memcpy(one + 1, in, in_len); - db_mask = para_malloc(mask_len); - mgf1(seed, HASH_SIZE, mask_len, db_mask); - for (n = 0; n < mask_len; n++) - db[n] ^= db_mask[n]; - mgf1(db, mask_len, HASH_SIZE, seed_mask); - for (n = 0; n < HASH_SIZE; n++) - seed[n] ^= seed_mask[n]; - free(db_mask); - dump_buffer("padded buffer", out, out_len); -} - -/* rfc 3447, section 7.1.2 */ -static int unpad_oaep(unsigned char *in, size_t in_len, unsigned char *out, - size_t *out_len) -{ - unsigned char *masked_seed = in + 1; - unsigned char *db = in + 1 + HASH_SIZE; - unsigned char seed[HASH_SIZE], seed_mask[HASH_SIZE]; - unsigned char *db_mask, *p; - size_t n, mask_len = in_len - HASH_SIZE - 1; - - mgf1(db, mask_len, HASH_SIZE, seed_mask); - for (n = 0; n < HASH_SIZE; n++) - seed[n] = masked_seed[n] ^ seed_mask[n]; - db_mask = para_malloc(mask_len); - mgf1(seed, HASH_SIZE, mask_len, db_mask); - for (n = 0; n < mask_len; n++) - db[n] ^= db_mask[n]; - free(db_mask); - if (memcmp(db, empty_hash, HASH_SIZE)) - return -E_OEAP; - for (p = db + HASH_SIZE; p < in + in_len - 1; p++) - if (*p != '\0') - break; - if (p >= in + in_len - 1) - return -E_OEAP; - p++; - *out_len = in + in_len - p; - memcpy(out, p, *out_len); - return 1; -} +/** S-expression for decryption. */ +#define RSA_DECRYPT_SEXP "(enc-val(flags oaep)(rsa(a %m)))" struct asymmetric_key { gcry_sexp_t sexp; @@ -301,64 +189,6 @@ static inline int get_long_form_num_length_bytes(unsigned char c) return c & 0x7f; } -static int find_pubkey_bignum_offset(const unsigned char *data, int len) -{ - const unsigned char *p = data, *end = data + len; - - /* the whole thing starts with one sequence */ - if (*p != ASN1_TYPE_SEQUENCE) - return -E_ASN1_PARSE; - p++; - if (p >= end) - return -E_ASN1_PARSE; - if (is_short_form(*p)) - p++; - else - p += 1 + get_long_form_num_length_bytes(*p); - if (p >= end) - return -E_ASN1_PARSE; - /* another sequence containing the object id, skip it */ - if (*p != ASN1_TYPE_SEQUENCE) - return -E_ASN1_PARSE; - p++; - if (p >= end) - return -E_ASN1_PARSE; - if (!is_short_form(*p)) - return -E_ASN1_PARSE; - p += 1 + get_short_form_length(*p); - if (p >= end) - return -E_ASN1_PARSE; - /* all numbers are wrapped in a bit string object that follows */ - if (*p != ASN1_TYPE_BIT_STRING) - return -E_ASN1_PARSE; - p++; - if (p >= end) - return -E_ASN1_PARSE; - if (is_short_form(*p)) - p++; - else - p += 1 + get_long_form_num_length_bytes(*p); - p++; /* skip number of unused bits in the bit string */ - if (p >= end) - return -E_ASN1_PARSE; - - /* next, we have a sequence of two integers (n and e) */ - if (*p != ASN1_TYPE_SEQUENCE) - return -E_ASN1_PARSE; - p++; - if (p >= end) - return -E_ASN1_PARSE; - if (is_short_form(*p)) - p++; - else - p += 1 + get_long_form_num_length_bytes(*p); - if (p >= end) - return -E_ASN1_PARSE; - if (*p != ASN1_TYPE_INTEGER) - return -E_ASN1_PARSE; - return p - data; -} - /* * Returns: Number of bytes scanned. This may differ from the value returned via * bn_bytes because the latter does not include the ASN.1 prefix and a leading @@ -460,6 +290,7 @@ static int get_private_key(const char *key_file, struct asymmetric_key **result) gcry_sexp_t sexp; struct asymmetric_key *key; + *result = NULL; ret = decode_key(key_file, PRIVATE_KEY_HEADER, PRIVATE_KEY_FOOTER, &blob); if (ret < 0) @@ -538,65 +369,6 @@ free_blob: return ret; } -/** Public keys start with this header. */ -#define PUBLIC_KEY_HEADER "-----BEGIN PUBLIC KEY-----" -/** Public keys end with this footer. */ -#define PUBLIC_KEY_FOOTER "-----END PUBLIC KEY-----" - -static int get_asn_public_key(const char *key_file, struct asymmetric_key **result) -{ - gcry_mpi_t n = NULL, e = NULL; - unsigned char *blob, *cp, *end; - int blob_size, ret, n_size; - gcry_error_t gret; - size_t erroff; - gcry_sexp_t sexp; - struct asymmetric_key *key; - - ret = decode_key(key_file, PUBLIC_KEY_HEADER, PUBLIC_KEY_FOOTER, - &blob); - if (ret < 0) - return ret; - blob_size = ret; - end = blob + blob_size; - ret = find_pubkey_bignum_offset(blob, blob_size); - if (ret < 0) - goto free_blob; - PARA_DEBUG_LOG("decoding public RSA params at offset %d\n", ret); - cp = blob + ret; - - ret = read_bignum(cp, end, &n, &n_size); - if (ret < 0) - goto free_blob; - cp += ret; - - ret = read_bignum(cp, end, &e, NULL); - if (ret < 0) - goto release_n; - - gret = gcry_sexp_build(&sexp, &erroff, RSA_PUBKEY_SEXP, n, e); - if (gret) { - PARA_ERROR_LOG("offset %zu: %s\n", erroff, - gcry_strerror(gcry_err_code(gret))); - ret = -E_SEXP_BUILD; - goto release_e; - } - key = para_malloc(sizeof(*key)); - key->sexp = sexp; - key->num_bytes = n_size; - *result = key; - ret = n_size; - PARA_INFO_LOG("successfully read %d bit asn public key\n", n_size * 8); - -release_e: - gcry_mpi_release(e); -release_n: - gcry_mpi_release(n); -free_blob: - free(blob); - return ret; -} - static int get_ssh_public_key(unsigned char *data, int size, gcry_sexp_t *result) { int ret; @@ -658,8 +430,7 @@ free_blob: return ret; } -int get_asymmetric_key(const char *key_file, int private, - struct asymmetric_key **result) +int get_public_key(const char *key_file, struct asymmetric_key **result) { int ret, ret2; void *map; @@ -668,17 +439,13 @@ int get_asymmetric_key(const char *key_file, int private, gcry_sexp_t sexp; struct asymmetric_key *key; - if (private) - return get_private_key(key_file, result); ret = mmap_full_file(key_file, O_RDONLY, &map, &map_size, NULL); if (ret < 0) return ret; ret = is_ssh_rsa_key(map, map_size); if (!ret) { - ret = para_munmap(map, map_size); - if (ret < 0) - return ret; - return get_asn_public_key(key_file, result); + para_munmap(map, map_size); + return -E_SSH_PARSE; } start = map + ret; end = map + map_size; @@ -699,7 +466,7 @@ unmap: return ret; } -void free_asymmetric_key(struct asymmetric_key *key) +void free_public_key(struct asymmetric_key *key) { if (!key) return; @@ -707,78 +474,27 @@ void free_asymmetric_key(struct asymmetric_key *key) free(key); } -static int decode_rsa(gcry_sexp_t sexp, int key_size, unsigned char *outbuf, - size_t *nbytes) +static int decode_rsa(gcry_sexp_t sexp, unsigned char *outbuf, size_t *nbytes) { - int ret; - gcry_error_t gret; - unsigned char oaep_buf[512]; - gcry_mpi_t out_mpi; - - if (libgcrypt_has_oaep) { - const char *p = gcry_sexp_nth_data(sexp, 1, nbytes); - - if (!p) { - PARA_ERROR_LOG("could not get data from list\n"); - return -E_OEAP; - } - memcpy(outbuf, p, *nbytes); - return 1; - } - out_mpi = gcry_sexp_nth_mpi(sexp, 0, GCRYMPI_FMT_USG); - if (!out_mpi) - return -E_SEXP_FIND; - gret = gcry_mpi_print(GCRYMPI_FMT_USG, oaep_buf, sizeof(oaep_buf), - nbytes, out_mpi); - if (gret) { - PARA_ERROR_LOG("mpi_print: %s\n", gcrypt_strerror(gret)); - ret = -E_MPI_PRINT; - goto out_mpi_release; - } - /* - * An oaep-encoded buffer always starts with at least one zero byte. - * However, leading zeroes in an mpi are omitted in the output of - * gcry_mpi_print() when using the GCRYMPI_FMT_USG format. The - * alternative, GCRYMPI_FMT_STD, does not work either because here the - * leading zero(es) might also be omitted, depending on the value of - * the second byte. - * - * To circumvent this, we shift the oaep buffer to the right. But first - * we check that the buffer actually started with a zero byte, i.e. that - * nbytes < key_size. Otherwise a decoding error occurred. - */ - ret = -E_SEXP_DECRYPT; - if (*nbytes >= key_size) - goto out_mpi_release; - memmove(oaep_buf + key_size - *nbytes, oaep_buf, *nbytes); - memset(oaep_buf, 0, key_size - *nbytes); + const char *p = gcry_sexp_nth_data(sexp, 1, nbytes); - PARA_DEBUG_LOG("decrypted buffer before unpad (%d bytes):\n", - key_size); - dump_buffer("non-unpadded decrypted buffer", oaep_buf, key_size); - ret = unpad_oaep(oaep_buf, key_size, outbuf, nbytes); - if (ret < 0) - goto out_mpi_release; - PARA_DEBUG_LOG("decrypted buffer after unpad (%zu bytes):\n", - *nbytes); - dump_buffer("unpadded decrypted buffer", outbuf, *nbytes); - ret = 1; -out_mpi_release: - gcry_mpi_release(out_mpi); - return ret; + if (!p) + return -E_RSA_DECODE; + memcpy(outbuf, p, *nbytes); + return 1; } int priv_decrypt(const char *key_file, unsigned char *outbuf, unsigned char *inbuf, int inlen) { gcry_error_t gret; - int ret, key_size; + int ret; struct asymmetric_key *priv; gcry_mpi_t in_mpi = NULL; gcry_sexp_t in, out, priv_key; size_t nbytes; - ret = check_key_file(key_file, true); + ret = check_private_key_file(key_file); if (ret < 0) return ret; PARA_INFO_LOG("decrypting %d byte input\n", inlen); @@ -786,7 +502,6 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf, ret = get_private_key(key_file, &priv); if (ret < 0) return ret; - key_size = ret / 8; /* asymmetric key priv -> sexp priv_key */ ret = -E_SEXP_FIND; @@ -803,7 +518,7 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf, goto key_release; } /* in_mpi -> in sexp */ - gret = gcry_sexp_build(&in, NULL, rsa_decrypt_sexp, in_mpi); + gret = gcry_sexp_build(&in, NULL, RSA_DECRYPT_SEXP, in_mpi); if (gret) { PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret)); ret = -E_SEXP_BUILD; @@ -817,7 +532,7 @@ int priv_decrypt(const char *key_file, unsigned char *outbuf, ret = -E_SEXP_DECRYPT; goto in_release; } - ret = decode_rsa(out, key_size, outbuf, &nbytes); + ret = decode_rsa(out, outbuf, &nbytes); if (ret < 0) goto out_release; PARA_INFO_LOG("successfully decrypted %zu byte message\n", nbytes); @@ -831,7 +546,8 @@ in_mpi_release: key_release: gcry_sexp_release(priv_key); free_key: - free_asymmetric_key(priv); + gcry_sexp_release(priv->sexp); + free(priv); return ret; } @@ -850,18 +566,7 @@ int pub_encrypt(struct asymmetric_key *pub, unsigned char *inbuf, pub_key = gcry_sexp_find_token(pub->sexp, "public-key", 0); if (!pub_key) return -E_SEXP_FIND; - if (libgcrypt_has_oaep) { - gret = gcry_sexp_build(&in, NULL, - "(data(flags oaep)(value %b))", len, inbuf); - } else { - unsigned char padded_input[256]; - const size_t pad_size = 256; - /* inbuf -> padded inbuf */ - pad_oaep(inbuf, len, padded_input, pad_size); - /* padded inbuf -> in sexp */ - gret = gcry_sexp_build(&in, NULL, - "(data(flags raw)(value %b))", pad_size, padded_input); - } + gret = gcry_sexp_build(&in, NULL, "(data(flags oaep)(value %b))", len, inbuf); if (gret) { PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret)); ret = -E_SEXP_BUILD; @@ -912,33 +617,20 @@ struct stream_cipher { gcry_cipher_hd_t handle; }; -struct stream_cipher *sc_new(const unsigned char *data, int len, - bool use_aes) +struct stream_cipher *sc_new(const unsigned char *data, int len) { gcry_error_t gret; struct stream_cipher *sc = para_malloc(sizeof(*sc)); - if (use_aes) { - assert(len >= 2 * AES_CRT128_BLOCK_SIZE); - gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_AES128, - GCRY_CIPHER_MODE_CTR, 0); - assert(gret == 0); - gret = gcry_cipher_setkey(sc->handle, data, - AES_CRT128_BLOCK_SIZE); - assert(gret == 0); - gret = gcry_cipher_setctr(sc->handle, - data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE); - assert(gret == 0); - return sc; - } - gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_ARCFOUR, - GCRY_CIPHER_MODE_STREAM, 0); - if (gret) { - PARA_ERROR_LOG("%s\n", gcrypt_strerror(gret)); - free(sc); - return NULL; - } - gret = gcry_cipher_setkey(sc->handle, data, (size_t)len); + assert(len >= 2 * AES_CRT128_BLOCK_SIZE); + gret = gcry_cipher_open(&sc->handle, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_CTR, 0); + assert(gret == 0); + gret = gcry_cipher_setkey(sc->handle, data, + AES_CRT128_BLOCK_SIZE); + assert(gret == 0); + gret = gcry_cipher_setctr(sc->handle, + data + AES_CRT128_BLOCK_SIZE, AES_CRT128_BLOCK_SIZE); assert(gret == 0); return sc; } diff --git a/ggo.c b/ggo.c deleted file mode 100644 index 81ab4644..00000000 --- a/ggo.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2008 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file ggo.c Function for printing help. */ - - -#include "para.h" -#include "ggo.h" -#include "version.h" - -/** - * Wrapper for printf() that exits on errors. - * - * \param fmt Usual format string. - * - * \return The return value of the underlying (successful) call to vprintf(3), - * i.e. the number of characters printed, excluding the terminating null byte. - */ -__printf_1_2 int printf_or_die(const char *fmt, ...) -{ - va_list argp; - int ret; - - va_start(argp, fmt); - ret = vprintf(fmt, argp); - va_end(argp); - if (ret >= 0) - return ret; - exit(EXIT_FAILURE); -} - -/** - * Print one of the two given help texts. - * - * \param help contains the help texts. - * \param flags What to print, see \ref ggo_print_help_flags. - */ -void ggo_print_help(struct ggo_help *help, unsigned flags) -{ - const char **p; - - if (help->purpose && (flags & GPH_PRINT_NAME_PURPOSE)) - printf_or_die("para_%s - %s\n", help->prefix, help->purpose); - if (help->usage && (flags & GPH_PRINT_USAGE)) - printf_or_die("\n%s\n", help->usage); - if (help->description && (flags & GPH_PRINT_DESCRIPTION)) - printf_or_die("\n%s\n", help->description); - printf_or_die("\n"); - if (flags & GPH_DETAILED) - p = help->detailed_help; - else - p = help->short_help; - if (!p) - return; - for (; *p; p++) - printf_or_die("%s\n", *p); -} diff --git a/ggo.h b/ggo.h deleted file mode 100644 index 7e524031..00000000 --- a/ggo.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2008 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file ggo.h Functions and structures for help text handling. */ - -/** - * Information extracted from the .cmdline.h header files. - */ -struct ggo_help { - /** Program or module (receiver, filter, writer) name. */ - const char *prefix; - /** Generated by gengetopt from the options in the .ggo file. */ - const char **short_help; - /** Like \a short_help, plus the \a details section. */ - const char **detailed_help; - /** The purpose text as specified in the ggo file. */ - const char *purpose; - /** Generated by gengetopt and exported via the *.cmdline.h file. */ - const char *usage; - /** The description text given in the .ggo file. */ - const char *description; -}; - -/** - * Control the output of \ref ggo_print_help(). - * - * Any combination of these flags may be passed to ggo_print_help(). - * Note that the list of supported options is always printed. - */ -enum ggo_print_help_flags { - /** Whether to print the short help or the detailed help. */ - GPH_DETAILED = 1 << 0, - /** Print the program or module name and the purpose text. */ - GPH_PRINT_NAME_PURPOSE = 1 << 1, - /** Print the synopsis. */ - GPH_PRINT_USAGE = 1 << 2, - /** Print the description text. */ - GPH_PRINT_DESCRIPTION = 1 << 3, -}; - -/** Used to print the normal help of programs (--help) */ -#define GPH_STANDARD_FLAGS \ - (GPH_PRINT_NAME_PURPOSE | GPH_PRINT_USAGE) - -/** Additional information for --detailed-help. */ -#define GPH_STANDARD_FLAGS_DETAILED \ - (GPH_STANDARD_FLAGS | GPH_DETAILED | GPH_PRINT_DESCRIPTION) - -/** For module help embedded in a program help. */ -#define GPH_MODULE_FLAGS 0 - -/** Modules help with detailed descriptions. */ -#define GPH_MODULE_FLAGS_DETAILED GPH_DETAILED | GPH_PRINT_DESCRIPTION - -/** Make a ggo_help structure using information from the .cmdline.h file. */ -#define DEFINE_GGO_HELP(_prefix) \ - { \ - .prefix = #_prefix, \ - .short_help = _prefix ## _args_info_help, \ - .detailed_help = _prefix ## _args_info_detailed_help, \ - .purpose = _prefix ## _args_info_purpose, \ - .usage = _prefix ## _args_info_usage, \ - .description = _prefix ## _args_info_description, \ - } - -void ggo_print_help(struct ggo_help *help, unsigned flags); -__printf_1_2 int printf_or_die(const char *fmt, ...); diff --git a/grab_client.c b/grab_client.c index 926f4792..11fff4cc 100644 --- a/grab_client.c +++ b/grab_client.c @@ -8,11 +8,14 @@ #include #include +#include +#include + +#include "audiod_cmd.lsg.h" #include "para.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "grab_client.h" #include "audiod.h" @@ -210,77 +213,62 @@ err: return ret; } -static int gc_check_args(int argc, char **argv, struct grab_client *gc) +static int gc_check_args(struct lls_parse_result *lpr, struct grab_client *gc) { - int i; + const struct lls_opt_result *r; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strncmp(arg, "-m", 2)) { - if (*(arg + 3)) - return -E_GC_SYNTAX; - switch(*(arg + 2)) { - case 's': - gc->mode = GM_SLOPPY; - continue; - case 'a': - gc->mode = GM_AGGRESSIVE; - continue; - case 'p': - gc->mode = GM_PEDANTIC; - continue; - default: - return -E_GC_SYNTAX; - } - } - if (!strcmp(arg, "-o")) { - gc->flags |= GF_ONE_SHOT; - continue; - } - if (!strncmp(arg, "-p=", 3)) { - gc->parent = para_strdup(arg + 3); - continue; - } - if (!strncmp(arg, "-n=", 3)) { - gc->name = para_strdup(arg + 3); - continue; - } - return -E_GC_SYNTAX; + r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_MODE, lpr); + if (lls_opt_given(r) > 0) { + const char *arg = lls_string_val(0, r); + if (strcmp(arg, "s") == 0) + gc->mode = GM_SLOPPY; + else if (strcmp(arg, "a") == 0) + gc->mode = GM_AGGRESSIVE; + else if (strcmp(arg, "p") == 0) + gc->mode = GM_PEDANTIC; + else + return -E_GC_SYNTAX; + } + + r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_ONE_SHOT, lpr); + if (lls_opt_given(r) > 0) + gc->flags |= GF_ONE_SHOT; + + r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_PARENT, lpr); + if (lls_opt_given(r) > 0) { + const char *arg = lls_string_val(0, r); + gc->parent = para_strdup(arg); + } + + r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_NAME, lpr); + if (lls_opt_given(r) > 0) { + const char *arg = lls_string_val(0, r); + gc->name = para_strdup(arg); } - if (i != argc) - return -E_GC_SYNTAX; return 1; } /** - * Check the command line options and allocate a grab_client structure. + * Create and activate a grab client. * * \param fd The file descriptor of the client. - * \param argc Argument count. - * \param argv Argument vector. + * \param lpr The parsed command line of the grab command. * \param s The scheduler to register the grab client task to. * - * If the command line options given by \a argc and \a argv are valid. - * allocate a struct grab_client and initialize it with this valid - * configuration. - * - * If the new grab client can be added to an existing buffer tree, activate it. - * Otherwise, add it to the inactive list for later activation. + * This function semantically parses the arguments given as options to the grab + * command. On success it allocates a struct grab_client, associates it with + * the given file descriptor and activates it. If the new grab client can not + * be attached to an existing buffer tree node it is put into the inactive list + * for later activation. * * \return Standard. */ -int grab_client_new(int fd, int argc, char **argv, struct sched *s) +int grab_client_new(int fd, struct lls_parse_result *lpr, struct sched *s) { int ret; struct grab_client *gc = para_calloc(sizeof(struct grab_client)); - ret = gc_check_args(argc, argv, gc); + ret = gc_check_args(lpr, gc); if (ret < 0) goto err_out; ret = dup(fd); diff --git a/grab_client.h b/grab_client.h index 7a752cee..3f3a0c03 100644 --- a/grab_client.h +++ b/grab_client.h @@ -6,5 +6,5 @@ /** \file grab_client.h exported symbols from grab_client.c */ -int grab_client_new(int fd, int argc, char **argv, struct sched *s); +int grab_client_new(int fd, struct lls_parse_result *lpr, struct sched *s); void activate_grab_clients(struct sched *s); diff --git a/gui.c b/gui.c index 8e83cc53..24f1c727 100644 --- a/gui.c +++ b/gui.c @@ -12,8 +12,9 @@ #include #include #include +#include -#include "gui.cmdline.h" +#include "gui.lsg.h" #include "para.h" #include "gui.h" #include "string.h" @@ -23,12 +24,20 @@ #include "list.h" #include "sched.h" #include "signal.h" -#include "ggo.h" #include "version.h" /** Array of error strings. */ DEFINE_PARA_ERRLIST; +static struct lls_parse_result *cmdline_lpr, *lpr; + +#define CMD_PTR (lls_cmd(0, gui_suite)) +#define OPT_RESULT(_name) (lls_opt_result(LSG_GUI_PARA_GUI_OPT_ ## _name, lpr)) +#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name))) +#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name))) +#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name))) +#define FOR_EACH_KEY_MAP(_i) for (_i = 0; _i < OPT_GIVEN(KEY_MAP); _i++) + static char *stat_content[NUM_STAT_ITEMS]; static struct gui_window { @@ -51,7 +60,6 @@ static unsigned scroll_position; static pid_t exec_pid; static int exec_fds[2] = {-1, -1}; -static struct gui_args_info conf; static int loglevel; /** Type of the process currently being executed. */ @@ -644,7 +652,8 @@ static int status_post_select(struct sched *s, void *context) if (tv_diff(&st->next_exec, now, NULL) > 0) return 0; st->next_exec.tv_sec = now->tv_sec + 2; - ret = para_exec_cmdline_pid(&st->pid, conf.stat_cmd_arg, fds); + ret = para_exec_cmdline_pid(&st->pid, + OPT_STRING_VAL(STAT_CMD), fds); if (ret < 0) return 0; ret = mark_fd_nonblocking(fds[1]); @@ -831,12 +840,13 @@ static void check_key_map_args_or_die(void) { int i; char *tmp = NULL; + const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP); - for (i = 0; i < conf.key_map_given; ++i) { + FOR_EACH_KEY_MAP(i) { char *handler, *arg; free(tmp); - tmp = para_strdup(conf.key_map_arg[i]); + tmp = para_strdup(lls_string_val(i, lor)); if (!split_key_map(tmp, &handler, &arg)) break; if (strlen(handler) != 1) @@ -849,77 +859,85 @@ static void check_key_map_args_or_die(void) if (find_cmd_byname(arg) < 0) break; } - if (i != conf.key_map_given) - die(EXIT_FAILURE, "invalid key map: %s\n", conf.key_map_arg[i]); + if (i != OPT_GIVEN(KEY_MAP)) + die(EXIT_FAILURE, "invalid key map: %s\n", + lls_string_val(i, lor)); free(tmp); } -static void parse_config_file_or_die(bool override) +static void parse_config_file_or_die(bool reload) { - bool err; - char *config_file; - struct gui_cmdline_parser_params params = { - .override = override, - .initialize = 0, - .check_required = !override, - .check_ambiguity = 0, - .print_errors = 1, - }; + int ret; + char *cf = NULL, *errctx = NULL; + void *map; + size_t sz; + int cf_argc; + char **cf_argv; + struct lls_parse_result *cf_lpr, *merged_lpr; - if (conf.config_file_given) - config_file = para_strdup(conf.config_file_arg); + if (OPT_GIVEN(CONFIG_FILE)) + cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE)); else { char *home = para_homedir(); - config_file = make_message("%s/.paraslash/gui.conf", home); + cf = make_message("%s/.paraslash/gui.conf", home); free(home); } - if (!file_exists(config_file)) { - if (!conf.config_file_given) - err = false; - else { - PARA_EMERG_LOG("config file %s does not exist\n", - config_file); - err = true; - } - goto out; - } - /* - * When the gengetopt config file parser is called more than once, any - * key map arguments found in the config file are _appended_ to the old - * values, even though we turn on ->override. We want the new arguments - * to replace the old ones, so we must empty the key_map_arg array - * first. Unfortunately, this also clears any key map arguments given - * at the command line. - */ - if (override) { - int i; - for (i = 0; i < conf.key_map_given; i++) { - free(conf.key_map_arg[i]); - conf.key_map_arg[i] = NULL; - } - conf.key_map_given = 0; + 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; + ret = 0; + lpr = cmdline_lpr; + goto success; } - - gui_cmdline_parser_config_file(config_file, &conf, ¶ms); - loglevel = get_loglevel_by_name(conf.loglevel_arg); + 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; + if (reload) /* config file overrides command line */ + ret = lls(lls_merge(cf_lpr, cmdline_lpr, CMD_PTR, &merged_lpr, + &errctx)); + else /* command line options overrride config file options */ + ret = lls(lls_merge(cmdline_lpr, cf_lpr, CMD_PTR, &merged_lpr, + &errctx)); + lls_free_parse_result(cf_lpr, CMD_PTR); + if (ret < 0) + goto free_cf; + if (lpr != cmdline_lpr) + lls_free_parse_result(lpr, CMD_PTR); + lpr = merged_lpr; +success: + loglevel = OPT_UINT32_VAL(LOGLEVEL); check_key_map_args_or_die(); - err = false; -out: - free(config_file); - if (err) + theme_init(OPT_STRING_VAL(THEME), &theme); +free_cf: + free(cf); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); - theme_init(conf.theme_arg, &theme); + } } /* reread configuration, terminate on errors */ static void reread_conf(void) { /* - * gengetopt might print to stderr and exit on errors. So we have to - * shutdown curses first. + * If the reload of the config file fails, we are about to exit. In + * this case we print the error message to stderr rather than to the + * curses window. So we have to shutdown curses first. */ shutdown_curses(); - parse_config_file_or_die(true /* override */); + parse_config_file_or_die(true); init_curses(); print_in_bar(COLOR_MSG, "config file reloaded\n"); } @@ -1077,12 +1095,13 @@ static void exec_external(char *file_and_args) static void handle_command(int c) { int i; + const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP); /* first check user-defined key bindings */ - for (i = 0; i < conf.key_map_given; ++i) { + FOR_EACH_KEY_MAP(i) { char *tmp, *handler, *arg; - tmp = para_strdup(conf.key_map_arg[i]); + tmp = para_strdup(lls_string_val(i, lor)); if (!split_key_map(tmp, &handler, &arg)) { free(tmp); return; @@ -1329,9 +1348,10 @@ static void com_reread_conf(void) static void com_help(void) { int i; + const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP); - for (i = 0; i < conf.key_map_given; ++i) { - char *handler, *arg, *tmp = para_strdup(conf.key_map_arg[i]); + FOR_EACH_KEY_MAP(i) { + char *handler, *arg, *tmp = para_strdup(lls_string_val(i, lor)); const char *handler_text = "???", *desc = NULL; if (!split_key_map(tmp, &handler, &arg)) { @@ -1415,15 +1435,6 @@ static void com_prev_theme(void) com_refresh(); } -__noreturn static void print_help_and_die(void) -{ - struct ggo_help h = DEFINE_GGO_HELP(gui); - bool d = conf.detailed_help_given; - - ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS); - exit(0); -} - static int setup_tasks_and_schedule(void) { int ret; @@ -1431,12 +1442,7 @@ static int setup_tasks_and_schedule(void) struct status_task status_task = {.fd = -1}; struct input_task input_task = {.task = NULL}; struct signal_task *signal_task; - struct sched sched = { - .default_timeout = { - .tv_sec = conf.timeout_arg / 1000, - .tv_usec = (conf.timeout_arg % 1000) * 1000, - }, - }; + struct sched sched = {.default_timeout = {.tv_sec = 1}}; exec_task.task = task_register(&(struct task_info) { .name = "exec", @@ -1476,6 +1482,21 @@ static int setup_tasks_and_schedule(void) return ret; } +static void handle_help_flags(void) +{ + char *help; + + if (OPT_GIVEN(DETAILED_HELP)) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP)) + help = lls_short_help(CMD_PTR); + else + return; + printf("%s\n", help); + free(help); + exit(EXIT_SUCCESS); +} + /** * The main function of para_gui. * @@ -1503,15 +1524,31 @@ static int setup_tasks_and_schedule(void) */ int main(int argc, char *argv[]) { - gui_cmdline_parser(argc, argv, &conf); /* exits on errors */ - loglevel = get_loglevel_by_name(conf.loglevel_arg); - version_handle_flag("gui", conf.version_given); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); - parse_config_file_or_die(false /* override */); + int ret; + char *errctx; + + ret = lls(lls_parse(argc, argv, CMD_PTR, &cmdline_lpr, &errctx)); + if (ret < 0) + goto out; + lpr = cmdline_lpr; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + version_handle_flag("gui", OPT_GIVEN(VERSION)); + handle_help_flags(); + parse_config_file_or_die(false); bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE); setlocale(LC_CTYPE, ""); initscr(); /* needed only once, always successful */ init_curses(); - return setup_tasks_and_schedule() < 0? EXIT_FAILURE : EXIT_SUCCESS; + ret = setup_tasks_and_schedule(); +out: + lls_free_parse_result(lpr, CMD_PTR); + if (lpr != cmdline_lpr) + lls_free_parse_result(cmdline_lpr, CMD_PTR); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + } + return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/http_recv.c b/http_recv.c index 2f334787..d49cf2a8 100644 --- a/http_recv.c +++ b/http_recv.c @@ -13,16 +13,16 @@ #include #include #include +#include +#include "recv_cmd.lsg.h" #include "para.h" #include "error.h" #include "http.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "recv.h" -#include "http_recv.cmdline.h" #include "net.h" #include "string.h" #include "fd.h" @@ -144,20 +144,13 @@ static void http_recv_close(struct receiver_node *rn) free(rn->private_data); } -static void *http_recv_parse_config(int argc, char **argv) -{ - struct http_recv_args_info *tmp = para_calloc(sizeof(*tmp)); - - http_recv_cmdline_parser(argc, argv, tmp); - return tmp; -} - static int http_recv_open(struct receiver_node *rn) { struct private_http_recv_data *phd; - struct http_recv_args_info *conf = rn->conf; - int fd, ret = para_connect_simple(IPPROTO_TCP, conf->host_arg, - conf->port_arg); + struct lls_parse_result *lpr = rn->lpr; + const char *r_i = RECV_CMD_OPT_STRING_VAL(HTTP, HOST, lpr); + uint32_t r_p = RECV_CMD_OPT_UINT32_VAL(HTTP, PORT, lpr); + int fd, ret = para_connect_simple(IPPROTO_TCP, r_i, r_p); if (ret < 0) return ret; @@ -174,30 +167,9 @@ static int http_recv_open(struct receiver_node *rn) return 1; } -static void http_recv_free_config(void *conf) -{ - http_recv_cmdline_parser_free(conf); - free(conf); -} - -/** - * The init function of the http receiver. - * - * \param r Pointer to the receiver struct to initialize. - * - * This initializes all function pointers of \a r. - */ -void http_recv_init(struct receiver *r) -{ - struct http_recv_args_info dummy; - - http_recv_cmdline_parser_init(&dummy); - r->open = http_recv_open; - r->close = http_recv_close; - r->pre_select = http_recv_pre_select; - r->post_select = http_recv_post_select; - r->parse_config = http_recv_parse_config; - r->free_config = http_recv_free_config; - r->help = (struct ggo_help)DEFINE_GGO_HELP(http_recv); - http_recv_cmdline_parser_free(&dummy); -} +const struct receiver lsg_recv_cmd_com_http_user_data = { + .open = http_recv_open, + .close = http_recv_close, + .pre_select = http_recv_pre_select, + .post_select = http_recv_post_select, +}; diff --git a/http_send.c b/http_send.c index 9d0f49ae..26536d07 100644 --- a/http_send.c +++ b/http_send.c @@ -13,11 +13,12 @@ #include #include #include +#include +#include "server.lsg.h" #include "para.h" #include "error.h" #include "string.h" -#include "server.cmdline.h" #include "afh.h" #include "server.h" #include "http.h" @@ -263,10 +264,10 @@ void http_send_init(struct sender *s) s->client_cmds[SENDER_add] = NULL; s->client_cmds[SENDER_delete] = NULL; - init_sender_status(hss, conf.http_access_arg, conf.http_access_given, - conf.http_port_arg, conf.http_max_clients_arg, - conf.http_default_deny_given); - if (conf.http_no_autostart_given) + init_sender_status(hss, OPT_RESULT(HTTP_ACCESS), + OPT_UINT32_VAL(HTTP_PORT), OPT_UINT32_VAL(HTTP_MAX_CLIENTS), + OPT_GIVEN(HTTP_DEFAULT_DENY)); + if (OPT_GIVEN(HTTP_NO_AUTOSTART)) return; ret = generic_com_on(hss, IPPROTO_TCP); if (ret < 0) diff --git a/imdct.c b/imdct.c index d2a06818..5791353b 100644 --- a/imdct.c +++ b/imdct.c @@ -141,28 +141,26 @@ __a_const static int split_radix_permutation(int i, int n) } /* z[0...8n - 1], w[1...2n - 1] */ -#define PASS(name)\ -static void name(struct fft_complex *z, const fftsample_t *wre, unsigned int n)\ -{\ - fftsample_t t1, t2, t3, t4, t5, t6;\ - int o1 = 2 * n;\ - int o2 = 4 * n;\ - int o3 = 6 * n;\ - const fftsample_t *wim = wre + o1;\ - n--;\ -\ - TRANSFORM_ZERO(z[0], z[o1], z[o2], z[o3]);\ - TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]);\ - do {\ - z += 2;\ - wre += 2;\ - wim -= 2;\ - TRANSFORM(z[0], z[o1], z[o2], z[o3], wre[0], wim[0]);\ - TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]);\ - } while (--n);\ +static void pass(struct fft_complex *z, const fftsample_t *wre, unsigned int n) +{ + fftsample_t t1, t2, t3, t4, t5, t6; + int o1 = 2 * n; + int o2 = 4 * n; + int o3 = 6 * n; + const fftsample_t *wim = wre + o1; + + n--; + TRANSFORM_ZERO(z[0], z[o1], z[o2], z[o3]); + TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]); + do { + z += 2; + wre += 2; + wim -= 2; + TRANSFORM(z[0], z[o1], z[o2], z[o3], wre[0], wim[0]); + TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]); + } while (--n); } -PASS(pass) #undef BUTTERFLIES #define BUTTERFLIES BUTTERFLIES_BIG diff --git a/interactive.c b/interactive.c index c34a0f6e..91ab1559 100644 --- a/interactive.c +++ b/interactive.c @@ -206,26 +206,7 @@ void i9e_attach_to_stdout(struct btr_node *producer) static void wipe_bottom_line(void) { - char x[] = " "; - int n = i9ep->num_columns; - - /* - * For reasons beyond my understanding, writing more than 68 characters - * here causes MacOS to mess up the terminal. Writing a line of spaces - * in smaller chunks works fine though. Weird. - */ - fprintf(i9ep->stderr_stream, "\r"); - while (n > 0) { - if (n >= sizeof(x)) { - fprintf(i9ep->stderr_stream, "%s", x); - n -= sizeof(x); - continue; - } - x[n] = '\0'; - fprintf(i9ep->stderr_stream, "%s", x); - break; - } - fprintf(i9ep->stderr_stream, "\r"); + fprintf(i9ep->stderr_stream, "\r%s\r", i9ep->empty_line); } #ifndef RL_FREE_KEYMAP_DECLARED diff --git a/ipc.c b/ipc.c index 9488224a..d7f515df 100644 --- a/ipc.c +++ b/ipc.c @@ -161,6 +161,27 @@ int shm_attach(int id, enum shm_attach_mode mode, void **result) return *result == (void *) -1? -ERRNO_TO_PARA_ERROR(errno) : 1; } +/** + * Get the size of a shared memory segment. + * + * \param id The shared memory segment identifier. + * \param result Size in bytes is returned here, zero on errors. + * + * \return Standard. + * + * \sa shmctl(2). + */ +int shm_size(int id, size_t *result) +{ + struct shmid_ds ds; /* data structure */ + + *result = 0; + if (shmctl(id, IPC_STAT, &ds) < 0) + return -ERRNO_TO_PARA_ERROR(errno); + *result = ds.shm_segsz; + return 1; +} + /** * Detach a shared memory segment. * diff --git a/ipc.h b/ipc.h index c8d31c0c..c4437104 100644 --- a/ipc.h +++ b/ipc.h @@ -11,4 +11,5 @@ int shm_new(size_t size); int shm_attach(int id, enum shm_attach_mode mode, void **result); int shm_detach(void *addr); int shm_destroy(int id); +int shm_size(int id, size_t *result); size_t shm_get_shmmax(void); diff --git a/m4/gengetopt/afh.m4 b/m4/gengetopt/afh.m4 deleted file mode 100644 index 9b8a6503..00000000 --- a/m4/gengetopt/afh.m4 +++ /dev/null @@ -1,99 +0,0 @@ -args "--unamed-opts=audio_file --no-handle-version --no-handle-help" - -purpose "Print information about audio file(s)" - -include(header.m4) -include(loglevel.m4) - - - -################################### -section "printing meta information" -################################### - -option "chunk-table" c -#~~~~~~~~~~~~~~~~~~~~~ -"print also the chunk table" -flag off -details = " - The 'chunk table' of an audio file is an array of offsets - within the audio file. Each offset corresponds to chunk - of encoded data. The exact meaning of 'chunk' depends on - the audio format. - - Programs which are unaware of the particular audio format can - read the chunk table to obtain the timing information needed - to stream the file. -" - -option "parser-friendly" p -#~~~~~~~~~~~~~~~~~~~~~~~~~ -"do not use human-readable output format" -flag off -details = " - Currently this option only affects the format of the chunk table, - so it has no effect if --chunk-table is not given. - - The human-readable output (the default) consists of one output - line per chunk and the output contains also the chunk number, - the duration and the size of each chunk. The parser-friendly - output prints only the offsets, in one line. -" - -############################# -section "modifying meta tags" -############################# - -option "modify" m -#~~~~~~~~~~~~~~~~ -"modify (rather than print) tags" -flag off -details = " - When this option is given, para_afh creates the result file - as a temporary copy of the given file(s), but with meta - tags changed according to the options below. On errors, - the temporary file is removed, leaving the original file - unchanged. On success, if --backup is given, the original - file is moved away. Finally the temporary file is renamed to - the name of the original file. -" - -option "backup" b -"create backup of the original file" -flag off -details = " - The backup suffix is '~', i.e. a single tilde character is appended - to the given file name. -" - -option "year" y -#~~~~~~~~~~~~~~ -"set the year tag" -string typestr="year" -optional - -option "title" t -#~~~~~~~~~~~~~~~ -"set the title tag" -string typestr="title" -optional - -option "artist" a -#~~~~~~~~~~~~~~~~ -"set the artist/author tag" -string typestr="artist" -optional - -option "album" A -#~~~~~~~~~~~~~~~ -"set the album tag" -string typestr="album" -optional - -option "comment" C -#~~~~~~~~~~~~~~~~~ -"set the comment tag" -string typestr="comment" -optional - - diff --git a/m4/gengetopt/afh_recv.m4 b/m4/gengetopt/afh_recv.m4 deleted file mode 100644 index 28a0a9ea..00000000 --- a/m4/gengetopt/afh_recv.m4 +++ /dev/null @@ -1,74 +0,0 @@ -args "--no-version --no-help" - -purpose "Make an audio stream from a local file" - -description " - The afh (audio format handler) receiver can be used to write - selected parts of the given audio file without decoding - the data. - - The selected parts of the content of the audio file are passed - to the child nodes of the buffer tree. Only complete chunks - with respect of the underlying audio format are passed. -" - -include(header.m4) - -option "filename" f -#~~~~~~~~~~~~~~~~~~ -"file to open" -string typestr = "filename" -required - -option "begin-chunk" b -#~~~~~~~~~~~~~~~~~~~~~ -"skip the beginning of the file" -int typestr = "chunk_num" -default = "0" -optional -details = " - The chunk_num argument must be between -num_chunks and - num_chunks - 1, inclusively, where num_chunks is the total - number of chunks of the audio file given by the argument to - --filename. If chunk_num is negative, the given number of - chunks are counted backwards from the end of the file. For - example --begin-chunk -100 instructs the afh receiver to - start output at chunk num_chunks - 100. This is useful for - selecting the last part of an audio file. -" - -option "end-chunk" e -#~~~~~~~~~~~~~~~~~~~ -"only write up to chunk chunk_num" -int typestr = "chunk_num" -optional -details = " - For the chunk_num argument the same rules as for --begin-chunk - apply. The default is to write up to the last chunk. -" - -option "just-in-time" j -#~~~~~~~~~~~~~~~~~~~~~~ -"use timed writes" -flag off -details = " - Write the specified chunks of data 'just in time', i.e. the - write of each chunk is delayed until the time it is needed - by the decoder/player in order to guarantee an uninterrupted - audio stream. This may be useful for third-party software - that is capable of reading from stdin. -" - -option "no-header" H -#~~~~~~~~~~~~~~~~~~~ -"do not write an audio file header" -flag off -details = " - If an audio format needs information about the audio file - in a format-specific header in order to be understood by - the decoding software, a suitable header is automatically - send. This option changes the default behaviour, i.e. no - header is written. -" - - diff --git a/m4/gengetopt/alsa_write.m4 b/m4/gengetopt/alsa_write.m4 deleted file mode 100644 index b2c56218..00000000 --- a/m4/gengetopt/alsa_write.m4 +++ /dev/null @@ -1,36 +0,0 @@ -args "--no-version --no-help" - -purpose "Native ALSA output plugin" - -include(header.m4) - - -option "device" d -#~~~~~~~~~~~~~~~~ -"set PCM device" -string typestr = "device" -default = "default" -optional -details = " - Check for the presence of a /proc/asound/ directory to see if - ALSA is present in your kernel. The file /proc/asound/devices - contains all devices ALSA knows about. -" - -option "buffer-time" B -#~~~~~~~~~~~~~~~~~~~~~ -"duration of the ALSA buffer" -int typestr = "milliseconds" -default = "170" -optional -details = " - This is only a hint as ALSA might pick a slightly different - time, depending on the sound hardware. The chosen value is - shown in debug output as BUFFER_TIME. - - If synchronization between multiple clients is desired, - the same buffer time should be configured for all clients. -" - - - diff --git a/m4/gengetopt/amp_filter.m4 b/m4/gengetopt/amp_filter.m4 deleted file mode 100644 index a02cc5b7..00000000 --- a/m4/gengetopt/amp_filter.m4 +++ /dev/null @@ -1,21 +0,0 @@ -args "--no-version --no-help" - -purpose "Amplify the decoded audio stream" - -option "amp" a -#~~~~~~~~~~~~~ -"amplification value" -int typestr="number" -default="32" -optional -details=" - The amplification value determines the scaling factor by - which the amplitude of the audio stream is multiplied. The - formula for the scaling factor is - - factor = 1 + amp / 64. - - For example, an amplifiction value of zero results in a - scaling factor of one while an amplification value of 64 - means to double the volume. -" diff --git a/m4/gengetopt/ao_write.m4 b/m4/gengetopt/ao_write.m4 deleted file mode 100644 index 29112d71..00000000 --- a/m4/gengetopt/ao_write.m4 +++ /dev/null @@ -1,30 +0,0 @@ -args "--no-version --no-help" - -purpose "Output plugin for libao" - -include(header.m4) - - -option "driver" d -#~~~~~~~~~~~~~~~~ -"Select a output driver by name" -string typestr = "name" -optional -details = " - If this is not given, the driver with the highest priority - (see below) will be used. -" - -option "ao-option" o -#~~~~~~~~~~~~~~~~~~~ -"Pass a key-value pair to the libao driver" -string typestr = "key:value" -optional -multiple -details = " - For each time this option is given, the supplied key-value - pair is appended to the list of options for the driver. Invalid - keys are silently ignored. -" - - diff --git a/m4/gengetopt/audioc.m4 b/m4/gengetopt/audioc.m4 deleted file mode 100644 index f216204f..00000000 --- a/m4/gengetopt/audioc.m4 +++ /dev/null @@ -1,26 +0,0 @@ -args "--unamed-opts=command --conf-parser --no-handle-version --no-handle-help" - -purpose "Communicate with para_audiod through a local socket" - -include(header.m4) - -option "socket" s -#~~~~~~~~~~~~~~~~ -"well-known socket (default=/var/paraslash/audiod.socket.$HOSTNAME)" - string typestr="filename" - optional - - -option "bufsize" b -#~~~~~~~~~~~~~~~~~ -"size of internal buffer" - int typestr="bytes" - default="8192" - optional - - -define(CURRENT_PROGRAM,para_audioc) -define(DEFAULT_HISTORY_FILE,~/.paraslash/audioc.history) -include(loglevel.m4) -include(history_file.m4) -include(complete.m4) diff --git a/m4/gengetopt/audiod.m4 b/m4/gengetopt/audiod.m4 deleted file mode 100644 index 75c04581..00000000 --- a/m4/gengetopt/audiod.m4 +++ /dev/null @@ -1,214 +0,0 @@ -args "--no-handle-help --no-handle-version --conf-parser" - -purpose "Connect to para_server, receive, decode and play audio streams" - -include(header.m4) -define(CURRENT_PROGRAM,para_audiod) -define(DEFAULT_CONFIG_FILE,~/.paraslash/audiod.conf) - - -######################### -section "General options" -######################### - - -include(loglevel.m4) -include(color.m4) -include(config_file.m4) -include(logfile.m4) -include(log_timing.m4) -include(daemon.m4) -include(user.m4) -include(group.m4) -include(priority.m4) - - -######################## -section "Audiod options" -######################## - -option "force" F -#~~~~~~~~~~~~~~~ -"force startup" -flag off -details=" - If this flag is not given, para_audiod refuses to start if the - well-known socket file (see the --socket option) already exists - because this usually means that para_audiod is already running - and listening on that socket. After a crash or if para_audiod - received a SIGKILL signal, a stale socket file might remain and - you have to use --force once to force startup of para_audiod. -" - -option "mode" m -#~~~~~~~~~~~~~~ -"startup mode" -string typestr="mode" -default="on" -optional -details=" - Para_audiod supports three modes of operation: On, off and - standby (sb). This option selects the mode that should be - used on startup. If para_audiod operates in \"on\" mode, it - will connect to para_server in order to receive its status - information. If para_server announces the availability of an - audio stream, para_audiod will automatically download, decode - and play the audio stream according to the given stream I/O - options, see below. - - In \"standby\" mode, para_audiod will only receive the - status information from para_server but will not download - the audio stream. - - In \"off\" mode, para_audiod does not connect para_server at - all, but still listens on the local socket for connections. -" - -option "socket" s -#~~~~~~~~~~~~~~~~ -"well-known socket" -string typestr="filename" -optional -details=" - Para_audiod uses a \"well-known\" socket to listen - on for connections from para_audioc. This socket is a - special file in the file system; its location defaults to - /var/paraslash/audiod_sock.. - - para_audioc, the client program used to connect to para_audiod, - opens this socket in order to talk to para_audiod. If the - default value for para_audiod is changed, para_audioc must be - instructed to use also \"filename\" for connecting para_audiod. -" - -option "user-allow" - -#~~~~~~~~~~~~~~~~~~~~ -"allow this user to connect to audiod" -string typestr = "username" -optional -multiple -details = " - Allow the user identified by username (either a string or - a UID) to connect to para_audiod. This option may be given - multiple times. If not specified at all, all users are allowed - to connect. - - This feature is based on the ability to send unix - credentials through local sockets using ancillary data - (SCM_CREDENTIALS). Currently it only works on Linux. On - other operating systems the option is silently ignored and - all local users are allowed to connect. -" - -option "clock-diff-count" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"sync clock on startup" -int typestr="count" -default="0" -optional -details=" - Check the clock difference between the host running para_server - and the local host running para_audiod that many times before - starting any stream I/0. Set this to non-zero for non-local - setups if the clocks of these two hosts are not synchronized - by ntp or similar. -" - -############################# -section "Stream I/O options" -############################# - -option "receiver" r -#~~~~~~~~~~~~~~~~~~ -"select receiver" -string typestr="receiver_spec" -default="http" -optional -multiple -details=" - This option may be given multiple times, for each audio format - separately. If multiple definitions for an audio format are - given, the first one is selected. - - The \"receiver_spec\" consists of an audio format specifier - and one or more receiver arguments, separated by a colon. - - The audio format specifier is a regular expression which - specifies the set of audio formats for which this option - should apply. - - If any receiver options are present, the whole receiver - argument must be quoted: - - -r 'mp3:http -i my.host.org -p 8009' - - Since a single dot '.' matches the name of any audio format, - specifying '.' instead of 'mp3' above activates the http - receiver for all audio formats. - -" - -option "filter" f -#~~~~~~~~~~~~~~~~ -"Specify the filter configuration." -string typestr = "filter_spec" -optional -multiple -details = " - This option may be given multiple times. The \"filter_spec\" - consists of an audio format specifier (see above), the name - of the filter, and any options for that filter. Note that - order matters. - - The compiled-in defaults apply to all audio formats for which - no --filter option was given. These defaults depend on the - receiver being used. - - For HTTP streams, only the decoder for the current audio - format is activated. UDP and DCCP streams, on the other - hand, are sent FEC-encoded by para_server. In order to play - such streams, the receiver output must be FEC-decoded first, - i.e. fed to the fecdec filter. Therefore the default for UDP - and DCCP streams is to activate the fecdec filter, followed - by the decoding filter for the audio format. - - Examples: - - --filter 'mp3:mp3dec' - - --filter 'mp3|aac:compress --inertia 5 --damp 2' - - --filter '.:fecdec' - -" - -option "writer" w -#~~~~~~~~~~~~~~~~ -"Specify stream writer." -string typestr="writer_spec" -optional -multiple -details=" - May be given multiple times, even multiple times for the same - audio format. Default value is \"alsa\" for all supported - audio formats. Example: - - --writer 'aac|wma:oss' - -" - -option "stream-delay" - -#~~~~~~~~~~~~~~~~~~~~~~ -"time for client sync" -int typestr="milliseconds" -default="200" -optional -details=" - Add the given amount of milliseconds to the stream start time - announced by para_server and do not send data to the writer - before that time (modulo clock difference). - - This is useful mainly for synchronizing the audio output of - different clients. -" - diff --git a/m4/gengetopt/channels.m4 b/m4/gengetopt/channels.m4 deleted file mode 100644 index 64c2518b..00000000 --- a/m4/gengetopt/channels.m4 +++ /dev/null @@ -1,14 +0,0 @@ - -option "channels" c -#~~~~~~~~~~~~~~~~~~ -"specify number of channels" -int typestr = "num" -default = "2" -optional -details = " - It is only necessary to specify this option for raw audio. If - it is not given, the channel count is queried from the parent - buffer tree nodes (e.g. the decoder) or the wav header. Only - if this fails, the default value applies. -" - diff --git a/m4/gengetopt/client.m4 b/m4/gengetopt/client.m4 deleted file mode 100644 index a5a27a05..00000000 --- a/m4/gengetopt/client.m4 +++ /dev/null @@ -1,46 +0,0 @@ -args "--unamed-opts=command --no-handle-error --conf-parser --no-handle-version --no-handle-help" - -purpose "Communicate with para_server through the paraslash control port" - -include(header.m4) -define(CURRENT_PROGRAM,para_client) -define(DEFAULT_CONFIG_FILE,~/.paraslash/client.conf) -define(DEFAULT_HISTORY_FILE,~/.paraslash/client.history) - -option "hostname" i -#~~~~~~~~~~~~~~~~~~ -"ip or host to connect" -string typestr = "host" -default = "localhost" -optional - -option "user" u -#~~~~~~~~~~~~~~ -"paraslash username" -string typestr = "username" -default = "" -optional - -option "server-port" p -#~~~~~~~~~~~~~~~~~~~~~ -"port to connect" -int typestr = "port" -default = "2990" -optional - -option "key-file" k -#~~~~~~~~~~~~~~~~~~ -"path to private key" -string typestr = "filename" -optional -details = " - If not given, the following files are tried, in order: - $HOME/.paraslash/key.$LOGNAME, $HOME/.ssh/id_rsa. It is a fatal - error if the key file can not be opened, or is world-readable. -" - - -include(loglevel.m4) -include(config_file.m4) -include(history_file.m4) -include(complete.m4) diff --git a/m4/gengetopt/color.m4 b/m4/gengetopt/color.m4 deleted file mode 100644 index eb081786..00000000 --- a/m4/gengetopt/color.m4 +++ /dev/null @@ -1,34 +0,0 @@ - - -option "color" C -#~~~~~~~~~~~~~~~ -"activate color output" -enum typestr="when" -values = "yes","no","auto" -default = "auto" -optional - -option "log-color" - -#~~~~~~~~~~~~~~~~~~~ -"select a color for one type of log message" -string typestr="color_spec" -multiple -optional -details=" - The format of \"color_spec\" is [fg [bg]] [attr]. - - Valid colors for \"fg\" and \"bg\" are \"normal\", \"black\", - \"red\", \"green\", \"yellow\", \"blue\", \"magenta\", - \"cyan\", and \"white\". - - The \"attr\" value must be one of \"bold\", \"dim\", \"ul\", - \"blink\", \"reverse\". - - Examples: - - --log-color \"debug:green\" - --log-color \"info:yellow bold\" - --log-color \"notice:white red bold\" -" - - diff --git a/m4/gengetopt/complete.m4 b/m4/gengetopt/complete.m4 deleted file mode 100644 index 14e737cf..00000000 --- a/m4/gengetopt/complete.m4 +++ /dev/null @@ -1,12 +0,0 @@ - -option "complete" - -#~~~~~~~~~~~~~~~~~~ -"print possible command line completions" - flag off - details = " - If this flag is given, CURRENT_PROGRAM reads the environment - variables COMP_LINE and COMP_POINT to obtain the current command line - and the cursor position respectively, prints possible completions - to stdout and exits. -" - diff --git a/m4/gengetopt/compress_filter.m4 b/m4/gengetopt/compress_filter.m4 deleted file mode 100644 index 501f46cc..00000000 --- a/m4/gengetopt/compress_filter.m4 +++ /dev/null @@ -1,41 +0,0 @@ -args "--no-version --no-help" - -purpose "Dynamically adjust the volume of an audio stream" - -option "blocksize" b -#~~~~~~~~~~~~~~~~~~~ -"adjust block size" -int typestr="number" -default="15" -optional -details = " - Larger blocksize means fewer volume adjustments per time unit. -" - -option "aggressiveness" a -#~~~~~~~~~~~~~~~~~~~~~~~~ - "controls the maximum amount to amplify by" -int typestr="number" -default="4" -optional - -option "inertia" i -#~~~~~~~~~~~~~~~~~ - "how much inertia ramping has" - int typestr="number" -default="6" -optional - -option "target-level" t -#~~~~~~~~~~~~~~~~~~~~~~ -"target signal level (0-32768)" -int typestr="number" -default="20000" -optional - -option "damp" d -#~~~~~~~~~~~~~~ -"if non-zero, scale down after normalizing" -int typestr="number" -default="0" -optional diff --git a/m4/gengetopt/config_file.m4 b/m4/gengetopt/config_file.m4 deleted file mode 100644 index 29f66b44..00000000 --- a/m4/gengetopt/config_file.m4 +++ /dev/null @@ -1,14 +0,0 @@ - -option "config-file" c -#~~~~~~~~~~~~~~~~~~~~~ -"(default='DEFAULT_CONFIG_FILE')" -string typestr="filename" -optional -details=" - CURRENT_PROGRAM reads its config file right after parsing - the options that were given at the command line. If an - option is given both at the command line and in the - config file, the value that was specified at the command line - takes precedence. -" - diff --git a/m4/gengetopt/daemon.m4 b/m4/gengetopt/daemon.m4 deleted file mode 100644 index ebead6a8..00000000 --- a/m4/gengetopt/daemon.m4 +++ /dev/null @@ -1,10 +0,0 @@ - -option "daemon" d -#~~~~~~~~~~~~~~~~ -"run as background daemon" -flag off -details = " - If this option is given and no logfile was specified, all - messages go to /dev/null. -" - diff --git a/m4/gengetopt/dccp_recv.m4 b/m4/gengetopt/dccp_recv.m4 deleted file mode 100644 index 1ba3fb59..00000000 --- a/m4/gengetopt/dccp_recv.m4 +++ /dev/null @@ -1,41 +0,0 @@ -args "--no-version --no-help" - -purpose "Receive a DCCP audio stream" - -option "host" i -"ip or host" -string default="localhost" -optional -details=" - Both IPv4 and IPv6 addresses are supported. -" - -option "port" p -"port to connect to" -int -default="8000" -optional - -option "ccid" c -"CCID preference(s) for this connection" -int -# restrict the maximum number of times this option can be passed -optional multiple(-10) -# currently known CCIDs: -# - CCID-2 (RFC 4341), -# - CCID-3 (RFC 4342), -# - CCID-4 (RFC 5622), -# - CCID-248 ... CCID-254 are experimental (RFC 4340, 19.5) -values="2", "3", "4", "248", "249", "250", "251", "252", "253", "254" -details=" - When present exactly once, this option mandates the CCID for the - sender-receiver connection. If it is passed more than once, it sets - a preference list where the order of appearance signifies descending - priority. For example, passing 4, 2, 3 creates the preference list - (CCID-4, CCID-2, CCID-3), assigning CCID-4 highest preference. - - The request is reconciled with the CCIDs on the server through the - 'server-priority' mechanism of RFC 4340 6.3.1/10. The server CCIDs - can be listed by calling 'para_client si'. - -" diff --git a/m4/gengetopt/fade.m4 b/m4/gengetopt/fade.m4 deleted file mode 100644 index 59389ffe..00000000 --- a/m4/gengetopt/fade.m4 +++ /dev/null @@ -1,246 +0,0 @@ -args "--conf-parser --no-handle-version --no-handle-help" - -purpose "An alarm clock and volume-fader for OSS and ALSA" - -include(header.m4) -define(CURRENT_PROGRAM,para_fade) -define(DEFAULT_CONFIG_FILE,~/.paraslash/fade.conf) - -section "General options" -######################### - - -include(config_file.m4) -include(loglevel.m4) - -option "mode" o -#~~~~~~~~~~~~~~ -"how to fade volume" - enum typestr = "mode" - values = "sleep", "fade", "set", "snooze" - default = "sleep" - optional - details=" - para_fade knows the following modes: - - sleep mode: Change to the initial volume and select - the initial afs mood/playlist. Then fade out until - the fade-out volume is reached. Switch to the - sleep mood/playlist until wake time minus fade-in - time. Finally switch to the wake mood/playlist and - fade to the fade-in volume. - - fade: Fade the volume to the given value in the - given time. - - set: Just set the value and exit. - - snooze: Fade out, sleep a bit and fade in. -" - -option "mixer-api" a -#~~~~~~~~~~~~~~~~~~~ -"choose the mixer API" - string typestr = "api" - optional - details = " - ALSA is preferred over OSS if both APIs are supported - and this option is not given. To see the supported - mixer APIs, use this option with an invalid string - as the mixer API, e.g. --mixer-api help. - " - -option "mixer-device" m -#~~~~~~~~~~~~~~~~~~~~~~ -"choose mixer device" - string typestr = "device" - optional - details = " - The default device (used if this option is not given) - depends on the selected mixer API. For ALSA, the - default is 'hw:0' which corresponds to the first sound - device. For OSS, '/dev/mixer' is used as the default. - " - -option "mixer-channel" C -#~~~~~~~~~~~~~~~~~~~~~~~ -"select the analog mixer channel" - string typestr = "channel" - optional - details = " - For the ALSA mixer API, the possible values are - determined at runtime depending on the hardware and - can be printed by specifying an invalid mixer channel, - for example --mixer-channel help. The default channel - is 'Master'. - - For OSS the possible values are invariably 'volume', - 'bass', 'treble', 'synth', 'pcm', 'speaker', 'line', - 'mic', 'cd', 'imix', 'altpcm', 'reclev', 'igain', - 'ogain'. However, not all listed channels might be - supported on any particular hardware. The default - channel is 'volume'. - " - -section "Options for sleep mode" -################################ - -option "ivol" - -#~~~~~~~~~~~~~~ -"set initial volume" - string typestr = "[channel:]volume" - default = "60" - optional - multiple - details = " - Used as the start volume, before fading out to the - fade out volume. The channel part may be omitted, in - which case the default channel is used. This option - may be given multiple times. - " - -option "fo-mood" - -#~~~~~~~~~~~~~~~~~ -"afs mood/playlist during fade out" - string typestr = "mood_spec" - default = "m/fade" - optional - details = " - Select this mood right after setting the - volume. Example: --fo-mood m/sleep -" - -option "fo-time" - -#~~~~~~~~~~~~~~~~~ -"fall asleep fade out time" - int typestr = "seconds" - default = "1800" - optional - details = " - No fading if set to 0. - " - -option "fo-vol" - -#~~~~~~~~~~~~~~~~ -"volume to fade out to" - int typestr = "volume" - default = "20" - optional - -option "sleep-mood" - -#~~~~~~~~~~~~~~~~~~~~ -"sleep time afs mood/playlist" - string typestr = "mood_spec" - default = "m/sleep" - optional - details = " - Select the given afs mood/playlist after the fade - out is complete. If unset, the \"stop\" command is - sent to para_server. - " - -option "wake-hour" H -#~~~~~~~~~~~~~~~~~~~ -"(0-23) (default: now + 9 hours)" - int typestr = "hour" - optional - -option "wake-min" M -#~~~~~~~~~~~~~~~~~~ -"(0-59)" - int typestr = "minutes" - default = "0" - optional - -option "fi-mood" - -#~~~~~~~~~~~~~~~~~ -"afs mood/playlist during fade in" - string typestr = "mood_spec" - default = "m/wake" - optional - details = " - Change to this afs mood/playlist on wake time. - " - -option "fi-time" - -#~~~~~~~~~~~~~~~~~ -"wake up fade in time" - int typestr="seconds" - default="1200" - optional - details = " - No fading in if set to 0. - " - -option "fi-vol" - -#~~~~~~~~~~~~~~~~ -"volume to fade to at wake time" - int typestr = "volume" - default = "80" - optional - -section "Options for snooze mode" -################################# - -option "so-time" - -#~~~~~~~~~~~~~~~~~ -"snooze-out time" - int typestr = "seconds" - default = "30" - optional - -option "so-vol" - -#~~~~~~~~~~~~~~~~ -"volume to fade to before snooze" - int typestr = "volume" - default = "20" - optional - -option "snooze-time" - -#~~~~~~~~~~~~~~~~~~~~~ -"delay" - int typestr = "seconds" - default = "600" - optional - -option "si-time" - -#~~~~~~~~~~~~~~~~~ -"snooze-in time" - int typestr = "seconds" - default = "180" - optional - -option "si-vol" - -#~~~~~~~~~~~~~~~~ -"volume to fade to after snooze" - int typestr = "volume" - default = "80" - optional - -section "Options for fade mode" -############################### - -option "fade-vol" f -#~~~~~~~~~~~~~~~~~~ -"volume to fade to" - int typestr = "volume" - default = "50" - optional - -option "fade-time" t -#~~~~~~~~~~~~~~~~~~~ -"fading time" - int typestr = "seconds" - default = "5" - optional - -section "Options for set mode" -############################## - -option "val" - -"value to set" - int typestr = "value" - default = "0" - optional - - diff --git a/m4/gengetopt/file_write.m4 b/m4/gengetopt/file_write.m4 deleted file mode 100644 index 4f98884f..00000000 --- a/m4/gengetopt/file_write.m4 +++ /dev/null @@ -1,13 +0,0 @@ -args "--no-version --no-help" - -purpose "Output plugin that writes to a local file" - -option "filename" f -#~~~~~~~~~~~~~~~~~~ -"specify output file name" -string typestr="filename" -optional -details=" - Defaults to a random filename in ~/.paraslash. -" - diff --git a/m4/gengetopt/filter.m4 b/m4/gengetopt/filter.m4 deleted file mode 100644 index b8b49f62..00000000 --- a/m4/gengetopt/filter.m4 +++ /dev/null @@ -1,25 +0,0 @@ -args "--no-handle-help --no-handle-version --conf-parser" - -purpose "Decode or process audio data from STDIN to STDOUT" - -include(header.m4) -include(loglevel.m4) - -option "filter" f -#~~~~~~~~~~~~~~~~ -"Specify filter." -string typestr="filter_spec" -optional -multiple -details=" - May be given multiple times to 'pipe' the stream through - arbitrary many filters (without copying the data). The same - filter may appear more than once, order matters. - - Options for a particular filter may be specified for each - given '--filter' option separately. You will have to quote - these options like this: - - --filter 'compress --inertia 5 --damp 2' -" - diff --git a/m4/gengetopt/group.m4 b/m4/gengetopt/group.m4 deleted file mode 100644 index 2a59ad9a..00000000 --- a/m4/gengetopt/group.m4 +++ /dev/null @@ -1,12 +0,0 @@ -option "group" g -#~~~~~~~~~~~~~~~ -"set group id" -string typestr="group" -optional -details=" - This option sets the group id according to 'group'. This option - is silently ignored if EUID != 0. Otherwise, real/effective - GID and the saved set-group ID are all set to the GID given by - 'group'. Must not be given in the config file. -" - diff --git a/m4/gengetopt/gui.m4 b/m4/gengetopt/gui.m4 deleted file mode 100644 index a6b718ee..00000000 --- a/m4/gengetopt/gui.m4 +++ /dev/null @@ -1,69 +0,0 @@ -args "--conf-parser --no-handle-version --no-handle-help" - -purpose "Show para_audiod status in a curses window" - -include(header.m4) -define(CURRENT_PROGRAM,para_gui) -define(DEFAULT_CONFIG_FILE,~/.paraslash/gui.conf) - - -######################### -section "General options" -######################### - - -include(config_file.m4) -include(loglevel.m4) - - -option "timeout" t -#~~~~~~~~~~~~~~~~~ -"set timeout" -int typestr = "milliseconds" -default = "30" -optional - -option "theme" T -#~~~~~~~~~~~~~~~ -"select startup theme" -string typestr = "name" -optional -details = " - If this option is not given the default theme is used. - If the given name is not a valid theme name, the list of - available themes is printed and the program terminates. -" - -option "stat-cmd" s -#~~~~~~~~~~~~~~~~~~ -"command to read status items from" -string typestr = "command" -default = "para_audioc -- stat -p" -optional -details = " - On a host on which the para_audiod service is not is running, the - default command will fail. An alternative is - - para_client -- stat -p - - This command connects para_server instead of para_audiod. However, - no timing information about the current audio file is printed. -" - -#--------------------------------- -section "Mapping keys to commands" -#--------------------------------- - -option "key-map" k -#~~~~~~~~~~~~~~~~~ -"Map key k to command c using mode m." - -string typestr = "k:m:c" -optional -multiple -details = " - Mode may be d, x or p for display, external and paraslash - commands, respectively. Of course, this option may be given - multiple times, one for each key mapping. -" - diff --git a/m4/gengetopt/header.m4 b/m4/gengetopt/header.m4 deleted file mode 100644 index c4231879..00000000 --- a/m4/gengetopt/header.m4 +++ /dev/null @@ -1 +0,0 @@ -changequote(,) diff --git a/m4/gengetopt/history_file.m4 b/m4/gengetopt/history_file.m4 deleted file mode 100644 index 73e98a78..00000000 --- a/m4/gengetopt/history_file.m4 +++ /dev/null @@ -1,12 +0,0 @@ - -option "history-file" - -#~~~~~~~~~~~~~~~~~~~~~~ -"(default='DEFAULT_HISTORY_FILE')" -string typestr = "filename" -optional -details = " - If CURRENT_PROGRAM runs in interactive mode, it reads the history - file on startup. Upon exit, the in-memory history is appended - to the history file. -" - diff --git a/m4/gengetopt/http_recv.m4 b/m4/gengetopt/http_recv.m4 deleted file mode 100644 index 6db3ff04..00000000 --- a/m4/gengetopt/http_recv.m4 +++ /dev/null @@ -1,23 +0,0 @@ -args "--no-version --no-help" - -purpose "Receive an HTTP audio stream" - -include(header.m4) - - -option "host" i -#~~~~~~~~~~~~~~ -"ip or host" -string -default="localhost" -optional -details=" - Both IPv4 and IPv6 addresses are supported. -" - -option "port" p -#~~~~~~~~~~~~~~ -"tcp port to connect to" -int default="8000" -optional - diff --git a/m4/gengetopt/log_timing.m4 b/m4/gengetopt/log_timing.m4 deleted file mode 100644 index ac0ea841..00000000 --- a/m4/gengetopt/log_timing.m4 +++ /dev/null @@ -1,12 +0,0 @@ - -option "log-timing" T -#~~~~~~~~~~~~~~~~~~~~ -"show milliseconds in log messages" -flag off -details = " - Selecting this option causes milliseconds to be included in - the log message output. This allows to measure the interval - between log messages in milliseconds which is useful for - identifying timing problems. -" - diff --git a/m4/gengetopt/logfile.m4 b/m4/gengetopt/logfile.m4 deleted file mode 100644 index 070d736b..00000000 --- a/m4/gengetopt/logfile.m4 +++ /dev/null @@ -1,11 +0,0 @@ - -option "logfile" L -#~~~~~~~~~~~~~~~~~ -"where to write log output" -string typestr="filename" -optional -details=" - If this option is not given, CURRENT_PROGRAM writes the log - messages to stderr. -" - diff --git a/m4/gengetopt/loglevel.m4 b/m4/gengetopt/loglevel.m4 deleted file mode 100644 index 162d030b..00000000 --- a/m4/gengetopt/loglevel.m4 +++ /dev/null @@ -1,22 +0,0 @@ - -option "loglevel" l -#~~~~~~~~~~~~~~~~~~ -"set loglevel" -string typestr="level" -values = "debug","info","notice","warning","error","crit","emerg" -default="warning" -optional -details=" - Log only messages with severity greater or equal the given - value. - - debug: Produces really noisy output. - info: Still noisy, but won't fill up the disk quickly. - notice: Indicates normal, but significant event. - warning: Unexpected events that can be handled. - error: Unhandled error condition. - crit: System might be unreliable. - emerg: Last message before exit. -" - - diff --git a/m4/gengetopt/makefile b/m4/gengetopt/makefile deleted file mode 100644 index 0db9d10e..00000000 --- a/m4/gengetopt/makefile +++ /dev/null @@ -1,27 +0,0 @@ -define ggo_opts - --output-dir=$(cmdline_dir) \ - --set-version="$(GIT_VERSION)" \ - --arg-struct-name=$(*F)_args_info \ - --file-name=$(*F).cmdline \ - --func-name=$(*F)_cmdline_parser \ - --set-package="para_$(*F)" -endef - -.PRECIOUS: $(cmdline_dir)/%.cmdline.c $(cmdline_dir)/%.cmdline.h $(ggo_dir)/%.ggo - -$(cmdline_dir)/%.cmdline.h $(cmdline_dir)/%.cmdline.c: $(ggo_dir)/%.ggo | $(cmdline_dir) - @[ -z "$(Q)" ] || echo 'GGO $<' - $(Q) $(GENGETOPT) $(ggo_opts) < $< -ifeq ($(ggo_descriptions_declared),no) - @echo 'extern const char *$(*F)_args_info_description;' >> $(cmdline_dir)/$(*F).cmdline.h -endif - -$(m4depdir)/%.m4d: $(m4_ggo_dir)/%.m4 | $(m4depdir) - @[ -z "$(Q)" ] || echo 'M4D $<' - $(Q) $(M4) -I $(m4_ggo_dir) -s $< \ - | awk '{if ($$1 ~ /#line/) {gsub(/"/, "", $$3); if ($$3 != "$<") \ - print "$(ggo_dir)/$(*F).ggo: " $$3}}' | sort | uniq > $@ - -$(ggo_dir)/%.ggo: $(m4_ggo_dir)/%.m4 $(m4_ggo_dir)/header.m4 | $(ggo_dir) - @[ -z "$(Q)" ] || echo 'M4 $<' - $(Q) $(M4) -I $(m4_ggo_dir) $< > $@ diff --git a/m4/gengetopt/mp3dec_filter.m4 b/m4/gengetopt/mp3dec_filter.m4 deleted file mode 100644 index 8b187835..00000000 --- a/m4/gengetopt/mp3dec_filter.m4 +++ /dev/null @@ -1,18 +0,0 @@ -args "--no-version --no-help" - -purpose "Decode an mp3 stream" - -include(header.m4) - - -option "ignore-crc" i -#~~~~~~~~~~~~~~~~~~~~ -"ignore CRC information in the audio stream." -flag off -details=" - This causes frames with CRC errors to be decoded and played - anyway. This option is not recommended, but since some encoders - have been known to generate bad CRC information, this option - is a work-around to play streams from such encoders. -" - diff --git a/m4/gengetopt/oss_write.m4 b/m4/gengetopt/oss_write.m4 deleted file mode 100644 index 578d8138..00000000 --- a/m4/gengetopt/oss_write.m4 +++ /dev/null @@ -1,11 +0,0 @@ -args "--no-version --no-help" - -purpose "Output plugin for the Open Sound System" - -option "device" d -#~~~~~~~~~~~~~~~~ -"set PCM device" -string typestr="device" -default="/dev/dsp" -optional -details = "" diff --git a/m4/gengetopt/osx_write.m4 b/m4/gengetopt/osx_write.m4 deleted file mode 100644 index 83ed737c..00000000 --- a/m4/gengetopt/osx_write.m4 +++ /dev/null @@ -1,17 +0,0 @@ -args "--no-version --no-help" - -purpose "Output plugin for Mac OS coreaudio" - -section "osx options" -##################### - -option "numbuffers" n -#~~~~~~~~~~~~~~~~~~~~~ - -"number of audio buffers to allocate (increase if -you get buffer underruns)" - - int typestr="num" - default="20" - optional - details = "" diff --git a/m4/gengetopt/play.m4 b/m4/gengetopt/play.m4 deleted file mode 100644 index 16875590..00000000 --- a/m4/gengetopt/play.m4 +++ /dev/null @@ -1,48 +0,0 @@ -args "--unamed-opts=audio_file --no-handle-version --conf-parser --no-handle-help" - -purpose "Command line audio player" - -description "para_play operates either in command mode or in insert -mode. In insert mode it presents a prompt and allows to enter commands -like stop, play, pause etc. In command mode the current audio file -is shown and the program reads single key strokes from stdin. Keys -may be mapped to commands. Whenever a mapped key is pressed, the -associated command is executed." - -include(header.m4) -define(CURRENT_PROGRAM,para_play) -define(DEFAULT_CONFIG_FILE,~/.paraslash/play.conf) -define(DEFAULT_HISTORY_FILE,~/.paraslash/play.history) - - -######################### -section "General options" -######################### - -include(loglevel.m4) -include(config_file.m4) -include(history_file.m4) - - -############################### -section "Options for para_play" -############################### - -option "randomize" z -#~~~~~~~~~~~~~~~~~~~ -"randomize playlist at startup." -flag off - -option "key-map" k -#~~~~~~~~~~~~~~~~~ -"Map key k to a command." - -string typestr = "key:command [args]" -optional -multiple -details = " - This option may be given multiple times, one for each key - mapping. Example: - 5:jmp 50 -" - diff --git a/m4/gengetopt/prebuffer_filter.m4 b/m4/gengetopt/prebuffer_filter.m4 deleted file mode 100644 index 20ff86fe..00000000 --- a/m4/gengetopt/prebuffer_filter.m4 +++ /dev/null @@ -1,28 +0,0 @@ -args "--no-version --no-help" - -purpose "Delay processing of an audio stream" - -option "duration" d -#~~~~~~~~~~~~~~~~~~ -"prebuffer time" -int typestr="milliseconds" -default="200" -optional -details=" - Wait that many milliseconds before letting data go through. - The time interval starts when the first data byte is seen by - this filter. -" - -option "size" s -#~~~~~~~~~~~~~~ -"amount of data to prebuffer" -int typestr="bytes" -default="0" -optional -details=" - Wait until that many data bytes are available in the input buffer. - The default value of zero means to not prebuffer by size at all. - If both --duration and --size options are given and non-zero, the - filter waits until both conditions are met. -" diff --git a/m4/gengetopt/priority.m4 b/m4/gengetopt/priority.m4 deleted file mode 100644 index 0b37dc06..00000000 --- a/m4/gengetopt/priority.m4 +++ /dev/null @@ -1,16 +0,0 @@ -option "priority" - -#~~~~~~~~~~~~~~~~~~ -"adjust scheduling priority" -int typestr = "prio" -default = "0" -optional -details = " - The priority (also known as nice value) is a value in the range -20 - to 19. Lower priorities cause more favorable scheduling. Since only - privileged processes may request a negative priority, specifying - a negative value works only if the daemon is started with root - privileges. - - Failure to set the given priority value is not considered an error - but a log message is printed in this case. -" diff --git a/m4/gengetopt/recv.m4 b/m4/gengetopt/recv.m4 deleted file mode 100644 index 9a9a8804..00000000 --- a/m4/gengetopt/recv.m4 +++ /dev/null @@ -1,20 +0,0 @@ -args "--no-handle-help --no-handle-version" - -purpose "A command line HTTP/DCCP/UDP stream grabber" - -include(header.m4) -include(loglevel.m4) - - -option "receiver" r -"Select receiver" -string typestr="receiver_spec" -default="http" -optional -details=" - Any options for the selected receiver must - be quoted. Example: - - -r 'http -i www.paraslash.org -p 8009' -" - diff --git a/m4/gengetopt/resample_filter.m4 b/m4/gengetopt/resample_filter.m4 deleted file mode 100644 index 2e5f4005..00000000 --- a/m4/gengetopt/resample_filter.m4 +++ /dev/null @@ -1,45 +0,0 @@ -args "--no-version --no-help" - -purpose "Transform raw audio to a different sample rate" - -include(header.m4) - -option "converter" C -#~~~~~~~~~~~~~~~~~~~ -"choose converter type" -enum typestr = "type" -values = "best", "medium", "fastest", "zero_order_hold", "linear" -default = "medium" -optional - -details = " - best: This is a bandlimited interpolator derived from the - mathematical sinc function and this is the highest quality - sinc based converter, providing a worst case Signal-to-Noise - Ratio (SNR) of 97 decibels (dB) at a bandwidth of 97%. - - medium: This is another bandlimited interpolator much like the - previous one. It has an SNR of 97dB and a bandwidth of 90%. The - speed of the conversion is much faster than the previous one. - - fastest: This is the fastest bandlimited interpolator and - has an SNR of 97dB and a bandwidth of 80%. - - zero_order_hold: A Zero Order Hold converter (interpolated - value is equal to the last value). The quality is poor but - the conversion speed is blindlingly fast. - - linear: A linear converter. Again the quality is poor, but - the conversion speed is blindingly fast. -" - -include(channels.m4) -include(sample_rate.m4) -include(sample_format.m4) - -option "dest-sample-rate" d -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"sample rate to convert to" -int typestr = "rate" -default = "44100" -optional diff --git a/m4/gengetopt/sample_format.m4 b/m4/gengetopt/sample_format.m4 deleted file mode 100644 index c998f9d6..00000000 --- a/m4/gengetopt/sample_format.m4 +++ /dev/null @@ -1,13 +0,0 @@ - -option "sample-format" f -#~~~~~~~~~~~~~~~~~~~~~~~ -"specify sample format" -# This must match the enum sample_format of para.h -values = "S8", "U8", "S16_LE", "S16_BE", "U16_LE", "U16_BE" enum -default = "S16_LE" -optional -details = " - It is only necessary to specify this for raw audio. See the - discussion of the --channels option. -" - diff --git a/m4/gengetopt/sample_rate.m4 b/m4/gengetopt/sample_rate.m4 deleted file mode 100644 index a8332a40..00000000 --- a/m4/gengetopt/sample_rate.m4 +++ /dev/null @@ -1,12 +0,0 @@ - -option "sample-rate" s -#~~~~~~~~~~~~~~~~~~~~~ -"do not guess the input sample rate" -int typestr = "num" -default = "44100" -optional -details = " - It is only necessary to specify this for raw audio. See the - discussion of the --channels option. -" - diff --git a/m4/gengetopt/server.m4 b/m4/gengetopt/server.m4 deleted file mode 100644 index 48e7a1f4..00000000 --- a/m4/gengetopt/server.m4 +++ /dev/null @@ -1,379 +0,0 @@ -args "--conf-parser --no-handle-version --no-handle-help" - -purpose "Manage and stream audio files" - -include(header.m4) -define(CURRENT_PROGRAM,para_server) -define(DEFAULT_CONFIG_FILE,~/.paraslash/server.conf) - - -######################### -section "General options" -######################### - - -include(loglevel.m4) -include(log_timing.m4) -include(color.m4) -include(daemon.m4) -include(user.m4) -include(group.m4) -include(priority.m4) - - -option "port" p -#~~~~~~~~~~~~~~ -"listening port" -int typestr="portnumber" -default="2990" -optional -details=" - para_server listens on this tcp port for incoming connections - from clients such as para_client. If the default port is - changed, the corresponding option of para_client must be used - to connect to para_server. -" - -############################# -section "Configuration files" -############################# - - -include(logfile.m4) -include(config_file.m4) - - -option "user-list" - -#~~~~~~~~~~~~~~~~~~~ -"(default='~/.paraslash/server.users')" - -string typestr="filename" -optional - - -################################## -section "virtual streaming system" -################################## - - -option "autoplay" a -#~~~~~~~~~~~~~~~~~~ -"start playing on startup" -flag off - -option "autoplay-delay" - -#~~~~~~~~~~~~~~~~~~~~~~~~ -"time to wait before streaming" -int typestr="ms" -default="0" -optional -dependon="autoplay" -details=" - If para_server is started with the autoplay option, this option - may be used to set up a delay before para_server streams its - first audio file. This is useful for example if para_server - and para_audiod are started during system startup. The delay - time should be choosen large enough so that para_audiod is - already up when para_server starts to stream. Of course, this - option depends on the autoplay option. -" -option "announce-time" A -#~~~~~~~~~~~~~~~~~~~~~~~ -"grace time for clients" - -int typestr="ms" -default="300" -optional -details=" - Clients such as para_audiod connect to para_server and execute - the stat command to find out whether an audio stream is - currently available. This sets the delay betweeen announcing - the stream via the output of the stat command and sending - the first chunk of data. -" - -############################# -section "audio file selector" -############################# - -option "afs-database-dir" D -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"location of the database" -string typestr="path" -optional -details=" - Where para_server should look for the osl database of the audio - file selector. The default is '~/.paraslash/afs_database-0.4'. -" - -option "afs-socket" s -#~~~~~~~~~~~~~~~~~~~~ -"Command socket for afs" -string typestr="path" -default="/var/paraslash/afs_command_socket-0.4" -optional -details=" - For each server command that is handled by the audio file - selector, the child process of para_server connects to the - audio file selector via a local socket. This option specifies - the location of that socket in the file system. -" -option "afs-initial-mode" i -#~~~~~~~~~~~~~~~~~~~~~~~~~~ - -"Mood or playlist to load on startup." -string typestr="/" -optional - -details=" - The argument of this option must be prefixed with either 'p/' - or 'm/' to indicate whether a playlist or a mood should be - loaded. Example: - --afs-initial-mode p/foo - loads the playlist named 'foo'. -" - -##################### -section "http sender" -##################### - - -option "http-port" - -#~~~~~~~~~~~~~~~~~~~ -"tcp port for http streaming" -int typestr="portnumber" -default="8000" -optional -details=" - The http sender of para_server listens on this port for - incoming connections. Clients are expected to send the usual - http request message such as 'GET / HTTP/'. -" - -option "http-default-deny" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"make the http ACL a whitelist" -flag off -details=" - The default is to use blacklists instead, i.e. connections - to the http sender are allowed unless the connecting host - matches a pattern given by a http-access option. This allows - to use access control the other way round: Connections are - denied from hosts which are not explicitly allowed by one or - more http-access options. -" - -option "http-access" - -#~~~~~~~~~~~~~~~~~~~~~ -"add an entry to the http ACL" -string typestr="a.b.c.d/n" -optional -multiple -details=" - Add given host/network to access control list (whitelist if - http-default-deny was given, blacklist otherwise) before - opening the tcp port. This option can be given multiple - times. Example: '192.168.0.0/24' whitelists/blacklists the - 256 hosts 192.168.0.x -" - -option "http-no-autostart" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"do not open tcp port on startup" -flag off -details=" - If this option is given, the http sender does not listen on - its tcp port. It may be instructed to open this port at a - later time by using the sender command. -" - -option "http-max-clients" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"maximal number of connections" -int typestr="number" -default="-1" -optional -details=" - The http sender will refuse connections if already that number - of clients are currently connected. A non-positive value - (the default) allows an unlimited number of simultaneous - connections. -" - -##################### -section "dccp sender" -##################### - - -option "dccp-port" - -#~~~~~~~~~~~~~~~~~~~ -"port for dccp streaming" -int typestr="portnumber" -default="8000" -optional -details=" - See http-port for details. -" - -option "dccp-default-deny" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"make the dccp ACL a whitelist" -flag off -details=" - See http-default-deny for details. -" - -option "dccp-access" - -#~~~~~~~~~~~~~~~~~~~~~ -"add an entry to the dccp ACL" -string typestr="a.b.c.d/n" -optional -multiple -details=" - See http-access for details. -" - -option "dccp-max-clients" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"maximal number of connections" -int typestr="number" -default="-1" -optional -details=" - See http-max-clients for details. -" - -option "dccp-max-slice-size" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"Upper bound for the FEC slice size" -int typestr = "size" -optional -default = "0" -details = " - If this value is non-positive (the default) the dccp sender - uses the maximum packet size (MPS) of the connection as the - slice size. The MPS is a network parameter and depends on - the path maximum transmission unit (path MTU) of an incoming - connection, i.e. on the largest packet size that can be - transmitted without causing fragmentation. - - This option allows to use a value less than the MPS in order - to fine-tune application performance. Values greater than - the MPS of an incoming connection can not be set. -" - -option "dccp-data-slices-per-group" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"The number of non-redundant slices per FEC group" -int typestr = "num" -optional -default = "3" -details = " - This determines the number of slices in each FEC group that are - necessary to decode the group. The given number must be smaller - than the value of the dccp-slices-per-group option below. - - Note that the duration of a FEC group is proportional to the - product dccp-max-slice-size * dccp-data-slices-per-group. -" - -option "dccp-slices-per-group" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"The total number of slices per FEC group" -int typestr = "num" -optional -default = "4" -details = " - This value must be larger than the value of the argument to - --dccp-data-slices-per-group. The difference of the two values is - the number of redundant slices, that is, the number of slices which - may be lost without causing interruptions in the audio stream. - - Increase this value if you are on a lossy network. -" - -#################### -section "udp sender" -#################### - -option "udp-target" - -#~~~~~~~~~~~~~~~~~~~~ -"add udp target with optional port" -string typestr="host[:port]" -optional -multiple -details=" - Add given host/port to the list of targets. The 'host' argument - can be either an IPv4/v6 address or hostname (RFC 3986 syntax). - The 'port' argument is an optional port number. If the 'port' - part is absent, the 'udp-default-port' value is used. - - The following examples are possible targets: - '10.10.1.2:8000' (host:port); '10.10.1.2' (with default port); - '224.0.1.38:1500' (IPv4 multicast); 'localhost:8001' (hostname - with port); '[::1]:8001' (IPv6 localhost); '[badc0de::1]' (IPv6 - host with default port); '[FF00::beef]:1500' (IPv6 multicast). - - This option can be given multiple times, for multiple targets. -" - -option "udp-no-autostart" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"do not start sending" -flag off -details=" - If this option is given, udp streaming may be activated at - a later time by using the sender command. -" - -option "udp-default-port" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"udp port to send to" -int typestr="port" -default="8000" -optional - -option "udp-mcast-iface" - -#~~~~~~~~~~~~~~~~~~~~~~~~~~ -"outgoing udp multicast interface" -string -optional - -option "udp-header-interval" H -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -"duration for sending header" -int typestr = "ms" -default = "2000" -optional -details = " - As the udp sender has no idea about connected clients it - sends the audio file header periodically if necessary. This - option specifies the duration between subsequent headers are - sent. Smaller values decrease the average time clients have - to wait before starting playback, larger values decrease - network traffic. - - Note that this affects only ogg/* and wma streams. Other - audio formats, including mp3, don't need an audio file header. -" - -option "udp-ttl" t -#~~~~~~~~~~~~~~~~~ -"set time to live value" -int typestr="num" -default="-1" -optional -details=" - This option applies exclusively to multicast UDPv4/v6 streaming. - - For the sending UDPv4 socket it sets the multicast Time-To-Live - value to \"num\". Traditional TTL scope values are: 0=host, - 1=network, 32=same site, 64=same region, 128=same continent, - 255=unrestricted. Please note however that this scoping is not - a good solution: RFC 2365 e.g. presents a better alternative. - - When using UDPv6 multicasting, the option sets the number of - multicast hops (as described in RFC 3493); a value of -1 - allows the kernel to auto-select the hop value. -" - diff --git a/m4/gengetopt/sync_filter.m4 b/m4/gengetopt/sync_filter.m4 deleted file mode 100644 index 1e6f5f82..00000000 --- a/m4/gengetopt/sync_filter.m4 +++ /dev/null @@ -1,45 +0,0 @@ -args "--no-version --no-help" - -purpose "Synchronize playback between multiple clients." - -option "buddy" b -#~~~~~~~~~~~~~~~ -"host to synchronize with" -multiple -string typestr = "url" -optional -details = " - This option may be given multiple times, one per buddy. Each - value may be given as a host, port pair in either IPv4 or - IPv6 form, with port being optional. If no port was specified - the listening port (as specified with --port, see below) - is used to send the synchronization packet to this buddy. -" - -option "port" p -#~~~~~~~~~~~~~~ -"UDP port for incoming synchronization packets" -int typestr = "portnumber" -default = "29900" -optional -details = " - The sync filter receives incoming synchronization packets on - this UDP port. -" - -option "timeout" t -#~~~~~~~~~~~~~~~~~ -"how long to wait for other clients" -int typestr = "milliseconds" -default = "2000" -optional -details = " - Once the sync filter receives its first chunk of input, a - synchronization period of the given number of milliseconds - begins. Playback is deferred until a synchronization packet - has been received from each defined buddy, or until the end - of the period. Buddies which did not send a synchronization - packet in time are temporarily disabled and are not waited for - during subsequent synchronization periods. They are re-enabled - automatically when another synchronization packet arrives. -" diff --git a/m4/gengetopt/udp_recv.m4 b/m4/gengetopt/udp_recv.m4 deleted file mode 100644 index dcdad4f7..00000000 --- a/m4/gengetopt/udp_recv.m4 +++ /dev/null @@ -1,21 +0,0 @@ -args "--no-version --no-help" - -purpose "Receive an UDP audio stream" - -option "host" i -"ip or host to receive udp packets from" -string default="224.0.1.38" -optional -details=" - The default address resolves to DANTZ.MCAST.NET and activates - multicast. -" - -option "port" p "udp port" -int typestr="portnumber" -default="8000" -optional - -option "iface" I "receiving udp multicast interface" -string -optional diff --git a/m4/gengetopt/user.m4 b/m4/gengetopt/user.m4 deleted file mode 100644 index 1bd5c59a..00000000 --- a/m4/gengetopt/user.m4 +++ /dev/null @@ -1,21 +0,0 @@ - -option "user" u -#~~~~~~~~~~~~~~ -"run as the given user" -string typestr="name" -optional -details=" - CURRENT_PROGRAM does not need any special privileges. - - If started as root (EUID == 0) this option must - be given at the command line (not in the configuration - file) so that CURRENT_PROGRAM can drop the root - privileges right after parsing the command line options, - but before parsing the configuration file. In this case, - real/effective/saved UID are all set to the UID of 'name'. As - the configuration file is read afterwards, those options that - have a default value depending on the UID (e.g. the directory - for the configuration file) are computed by using the uid of - 'name'. This option has no effect if CURRENT_PROGRAM - is started as a non-root user (i.e. EUID != 0). -" diff --git a/m4/gengetopt/write.m4 b/m4/gengetopt/write.m4 deleted file mode 100644 index 6667cb8c..00000000 --- a/m4/gengetopt/write.m4 +++ /dev/null @@ -1,22 +0,0 @@ -args "--no-handle-help --no-handle-version" - -purpose "Play wav or raw audio" - -include(header.m4) -include(loglevel.m4) - -option "writer" w -#~~~~~~~~~~~~~~~~ -"select stream writer" -string typestr="name" -optional -multiple -details=" - May be given multiple times, and the same writer may be specified more - than once. If this option is not given, the first supported writer - is started. The list of supported writers is shown in the help output. -" - -include(channels.m4) -include(sample_rate.m4) -include(sample_format.m4) diff --git a/m4/lls/afh.suite.m4 b/m4/lls/afh.suite.m4 new file mode 100644 index 00000000..cf83b972 --- /dev/null +++ b/m4/lls/afh.suite.m4 @@ -0,0 +1,94 @@ +m4_define(PROGRAM, para_afh) +[suite afh] +version-string = GIT_VERSION() +[supercommand para_afh] + purpose = print or modify meta information about audio file(s) + [description] + By default para_afh prints information about the given audio files, + such as format, duration, bitrate, etc., and the content of the most + common meta tags (artist, title, album, year, comment). In modify + mode it changes the meta tags to the values given at the command line. + [/description] + non-opts-name = file... + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(loglevel.m4) + [option modify-section] + summary = Printing meta information + flag ignored + [option chunk-table] + short_opt = c + summary = print also the chunk table + [help] + The chunk table of an audio file is an array of offsets where each + offset corresponds to chunk of encoded data. The exact meaning of + chunk depends on the audio format. + + Programs which are unaware of the particular audio format can read the + chunk table to obtain the timing information needed to stream the file. + [/help] + [option parser-friendly] + short_opt = p + summary = do not use human-readable output format + [help] + This option only affects the format of the chunk table, so it has no + effect if --chunk-table is not given. + + The human-readable output (the default) consists of one output line + per chunk and the output contains also the chunk number, the duration + and the size of each chunk. The parser-friendly output prints only + the offsets, in one line. + [/help] + [option modify-section] + summary = Modifying meta tags + flag ignored + [option modify] + short_opt = m + summary = modify (rather than print) tags + [help] + If this option is given, para_afh creates a temporary copy of the given + file(s), with meta tags changed according to the options below. On + errors, the temporary file is removed, leaving the original file + unchanged. On success, if --backup is given, the original file is + moved away. Finally the temporary file is renamed to the name of the + original file. + [/help] + [option backup] + short_opt = b + summary = create backup of the original file + [help] + The backup suffix is '~'. That is, a single tilde character is appended + to the given file name. + [/help] + [option year] + short_opt = y + summary = set the year tag + arg_info = required_arg + arg_type = string + typestr = string + [option title] + short_opt = t + summary = set the title tag + arg_info = required_arg + arg_type = string + typestr = string + [option artist] + short_opt = a + summary = set the artist tag + arg_info = required_arg + arg_type = string + typestr = string + [option album] + short_opt = A + summary = set the album tag + arg_info = required_arg + arg_type = string + typestr = string + [option comment] + short_opt = C + summary = set the comment tag + arg_info = required_arg + arg_type = string + typestr = string diff --git a/m4/lls/audioc.suite.m4 b/m4/lls/audioc.suite.m4 new file mode 100644 index 00000000..1c037bc7 --- /dev/null +++ b/m4/lls/audioc.suite.m4 @@ -0,0 +1,39 @@ +m4_define(PROGRAM, para_audioc) +m4_define(DEFAULT_HISTORY_FILE, ~/.paraslash/audioc.history) +[suite audioc] +version-string = GIT_VERSION() +[supercommand para_audioc] + purpose = communicate with para_audiod through a local socket + non-opts-name = [command [options]] + [description] + The client program to control para_audiod at runtime. It allows to + enable/disable streaming, to receive status info, or to grab the + audio stream at any point of the decoding process. + + If no command is given, para_audioc enters interactive mode. + [/description] + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(loglevel.m4) + m4_include(complete.m4) + m4_include(history-file.m4) + m4_include(per-command-options-section.m4) + [option socket] + short_opt = s + summary = path to well-known socket + arg_info = required_arg + arg_type = string + typestr = path + [help] + The default path of the socket is + /var/paraslash/audiod.socket.$HOSTNAME. + [/help] + [option bufsize] + short_opt = b + summary = size of internal buffer + arg_info = required_arg + arg_type = uint32 + typestr = bytes + default_val = 8192 diff --git a/m4/lls/audiod.suite.m4 b/m4/lls/audiod.suite.m4 new file mode 100644 index 00000000..31f60425 --- /dev/null +++ b/m4/lls/audiod.suite.m4 @@ -0,0 +1,191 @@ +m4_define(PROGRAM, para_audiod) +m4_define(DEFAULT_CONFIG_FILE, ~/.paraslash/audiod.conf) +[suite audiod] +version-string = GIT_VERSION() +[supercommand para_audiod] + purpose = connect to para_server, receive, decode and play audio streams + [description] + para_audiod runs on a host with an audio device and connects + para_server to obtain status information and receive the current audio + stream. The stream is transformed through any number of filters and + then written to the configured output device. + + Moreover, para_audiod listens on a local socket and sends status + information to local clients on request. Access to the local socket + can be restricted by means of Unix socket credentials, if available. + [/description] + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(config-file.m4) + m4_include(loglevel.m4) + m4_include(logfile.m4) + m4_include(color.m4) + m4_include(log-timing.m4) + m4_include(daemon.m4) + m4_include(user.m4) + m4_include(group.m4) + m4_include(priority.m4) + m4_include(per-command-options-section.m4) + [option force] + summary = force startup + short_opt = F + [help] + If this flag is not given, para_audiod refuses to start if the + well-known socket file (see the --socket option) already exists + because this usually means that para_audiod is already running and + listening on that socket. After a crash or if para_audiod received + a SIGKILL signal, a stale socket file might remain and you have to + use --force once to force startup of para_audiod. + [/help] + [option mode] + summary = select startup mode + arg_info = required_arg + arg_type = string + typestr = mode + values = {AUDIOD_ON = "on", AUDIOD_OFF = "off", AUDIOD_STANDBY = "sb"} + default_val = on + [help] + para_audiod supports three modes of operation: On, off and standby + (sb). This option selects the mode that should be used at startup. If + mode is "on", para_audiiod connects para_server to receive status + information. If the server announces the availability of an audio + stream, para_audiod downloads, decodes and plays the audio stream + according to the given stream I/O options, see below. + + In "standby" mode, para_audiod only receives the status information + from para_server but does not download the audio stream. + + In "off" mode, para_audiod does not connect para_server at all, + but still listens on the local socket. + [/help] + [option socket] + short_opt = s + summary = path to the well-known socket + arg_info = required_arg + arg_type = string + typestr = path + [help] + para_audiod listens on a "well-known" socket for connections + from para_audioc. This socket is a special file in the file system; + its location defaults to /var/paraslash/audiod_sock.$hostname. + + para_audioc, the client program used to connect to para_audiod, + opens this socket in order to talk to para_audiod. If the default + value for para_audiod is changed, para_audioc must be instructed to + use also to connect para_audiod. + [/help] + [option user-allow] + summary = allow this user to connect to audiod + arg_info = required_arg + arg_type = string + typestr = username + [help] + Allow the user identified by username (either a string or a UID) to + connect to para_audiod. This option may be given multiple times. If + not specified at all, all users are allowed to connect. + + This feature is based on the ability to send unix credentials through + local sockets using ancillary data (SCM_CREDENTIALS). Currently it + only works on Linux. On other operating systems the option is silently + ignored and all local users are allowed to connect. + [/help] + [option clock-diff-count] + summary = sync clock on startup + arg_info = required_arg + arg_type = uint32 + typestr = count + [help] + Check the clock difference between the host running para_server and + the local host running para_audiod this many times before starting + any stream I/0. Set this to non-zero for non-local setups if the + clocks of these two hosts are not synchronized by ntp or similar. + [/help] + [option Stream-IO-options] + summary = Stream I/O options + flag ignored + [option receiver] + short_opt = r + summary = select receiver + arg_info = required_arg + arg_type = string + typestr = receiver_spec + default_val = http + flag multiple + [help] + This option may be given multiple times, for each audio format + separately. If multiple definitions for an audio format are given, + the first one is selected. + + The consists of an audio format specifier and one or + more receiver arguments, separated by a colon. + + The audio format specifier is a regular expression which specifies + the set of audio formats for which this option should apply. + + If any receiver options are present, the whole receiver argument must + be quoted: + + -r 'mp3:http -i my.host.org -p 8009' + + Since a single dot '.' matches the name of any audio format, specifying + '.' instead of 'mp3' above activates the http receiver for all audio + formats. + [/help] + [option filter] + short_opt = f + summary = specify the filter configuration + arg_info = required_arg + arg_type = string + typestr = filter_spec + flag multiple + [help] + This option may be given multiple times. The +Andre Noll +.ME diff --git a/m4/lls/filter.suite.m4 b/m4/lls/filter.suite.m4 new file mode 100644 index 00000000..0cd69ebd --- /dev/null +++ b/m4/lls/filter.suite.m4 @@ -0,0 +1,37 @@ +m4_define(PROGRAM, para_filter) +[suite filter] +version-string = GIT_VERSION() +[supercommand para_filter] + purpose = decode or process audio data from STDIN to STDOUT + [description] + This program allows to specify a chain of filters which transform the + audio stream read from STDIN. A common mode of operation is to decode + an mp3 file with the mp3dec filter, but many other filters exist which + transform the audio stream in different ways. + [/description] + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(loglevel.m4) + m4_include(per-command-options-section.m4) + [option filter] + short_opt = f + summary = add one filter to the filter chain + arg_info = required_arg + arg_type = string + flag multiple + typestr = filter_spec + [help] + A filter specifier begins with the name of a supported filter, + optionally followed by zero or more options for the named filter. + Filter name and options must be separated by whitespace. If the + there is at least one option, the filter specifier needs to be quoted + like this: + + --filter 'compress --inertia 5 --damp 2' + + This option may be specified multiple times to 'pipe' the stream + through all given filters (in a single thread without copying the + data). The same filter may appear more than once, and order matters. + [/help] diff --git a/m4/lls/filter_cmd.suite.m4 b/m4/lls/filter_cmd.suite.m4 new file mode 100644 index 00000000..d269d237 --- /dev/null +++ b/m4/lls/filter_cmd.suite.m4 @@ -0,0 +1,205 @@ +[suite filter_cmd] +caption = filters +[subcommand aacdec] + purpose = decode an aac stream +[subcommand amp] + purpose = amplify (scale) a raw audio stream + [option amp] + short_opt = a + summary = amplification value + typestr = number + arg_info = required_arg + arg_type = uint32 + default_val = 32 + [help] + The amplification value determines the scaling factor by which the + amplitude of the audio stream is multiplied. The formula for the + scaling factor is + + factor = 1 + amp / 64. + + For example, an amplification value of zero results in a scaling factor + of one while an amplification value of 64 means to double the volume. + [/help] +[subcommand compress] + purpose = dynamically adjust the volume of an audio stream + [option blocksize] + short_opt = b + summary = use blocks of size 2**bits + typestr = bits + arg_info = required_arg + arg_type = uint32 + default_val = 15 + [help] + Larger blocksize means fewer volume adjustments per time unit. + [/help] + [option aggressiveness] + short_opt = a + summary = controls the maximum amount to amplify by + typestr = bits + arg_info = required_arg + arg_type = uint32 + default_val = 4 + [option inertia] + short_opt = i + summary = how much inertia ramping has + typestr = bits + arg_info = required_arg + arg_type = uint32 + default_val = 6 + [option target-level] + short_opt = t + summary = target signal level (0-32768) + typestr = level + arg_info = required_arg + arg_type = uint32 + default_val = 20000 + [option damp] + short_opt = d + summary = if non-zero, scale down after normalizing + typestr = bits + arg_info = required_arg + arg_type = uint32 + default_val = 0 +[subcommand fecdec] + purpose = decode a (lossy) input stream using forward error correction +[subcommand flacdec] + purpose = decode a flac stream +[subcommand mp3dec] + purpose = decode an mp3 stream + [option ignore-crc] + short_opt = i + summary = ignore CRC information in the audio stream + [help] + This causes frames with CRC errors to be decoded and played + anyway. This option is not recommended, but since some encoders + have been known to generate bad CRC information, this option is a + work-around to play streams from such encoders. + [/help] +[subcommand oggdec] + purpose = decode an ogg/vorbis stream +[subcommand opusdec] + purpose = decode an ogg/opus stream +[subcommand prebuffer] + purpose = delay processing of an audio stream + [option duration] + short_opt = d + summary = length of the prebuffer period + typestr = milliseconds + arg_info = required_arg + arg_type = uint32 + default_val = 200 + [help] + Wait this many milliseconds before letting data go through. The time + interval starts when the first data byte is seen in the input queue. + [/help] + [option size] + short_opt = s + summary = amount of data to prebuffer + typestr = bytes + arg_info = required_arg + arg_type = uint32 + default_val = 0 + [help] + Wait until this many data bytes are available in the input queue. The + default value of zero means to not prebuffer by size. If both + --duration and --size are given and non-zero, the prebuffer filter + waits until both conditions are met. + [/help] +[subcommand resample] + purpose = transform raw audio to a different sample rate + [option converter] + short_opt = C + summary = set conversion algorithm + typestr = type + arg_info = required_arg + arg_type = string + values = { + # RCT: resample conversion type + RCT_BEST = "best", + RCT_MEDIUM = "medium", + RCT_FASTEST = "fastest", + RCT_ZERO_ORDER_HOLD = "zero_order_hold", + RCT_LINEAR = "linear" + } + default_val = medium + [help] + best: This is a bandlimited interpolator derived from the mathematical + sinc function and this is the highest quality sinc based converter, + providing a worst case Signal-to-Noise Ratio (SNR) of 97 decibels + (dB) at a bandwidth of 97%. + + medium: This is another bandlimited interpolator much like the previous + one. It has an SNR of 97dB and a bandwidth of 90%. The speed of the + conversion is much faster than the previous one. + + fastest: This is the fastest bandlimited interpolator and has an SNR + of 97dB and a bandwidth of 80%. + + zero_order_hold: A Zero Order Hold converter (interpolated value + is equal to the last value). The quality is poor but the conversion + speed is blindlingly fast. + + linear: A linear converter. Again the quality is poor, but the + conversion speed is blindingly fast. + [/help] + [option dest-sample-rate] + short_opt = d + summary = sample rate to convert to + typestr = rate + arg_info = required_arg + arg_type = uint32 + default_val = 44100 + m4_include(channels.m4) + m4_include(sample-rate.m4) + m4_include(sample-format.m4) +[subcommand spxdec] + purpose = decode an ogg/speex stream +[subcommand sync] + purpose = synchronize playback between multiple clients + [option buddy] + short_opt = b + summary = client to synchronize with + typestr = url + arg_info = required_arg + arg_type = string + flag multiple + [help] + This option may be given multiple times, one per buddy. Each value + may be given as a host, port pair in either IPv4 or IPv6 form, with + port being optional. If no port was specified the listening port (as + specified with --port, see below) is used to send the synchronization + packet to this buddy. + [/help] + [option port] + short_opt = p + summary = UDP port for incoming synchronization packets + typestr = portnumber + arg_info = required_arg + arg_type = uint32 + default_val = 29900 + [help] + The sync filter expects incoming synchronization packets on this + UDP port. + [/help] + [option timeout] + short_opt = t + summary = how long to wait for other clients + typestr = milliseconds + arg_info = required_arg + arg_type = uint32 + default_val = 2000 + [help] + Once the sync filter receives its first chunk of input, + a synchronization period of the given number of milliseconds + begins. Playback is deferred until a synchronization packet has + been received from each defined buddy, or until the end of the + period. Buddies which did not send a synchronization packet in time + are temporarily disabled and are not waited for during subsequent + synchronization periods. They are re-enabled automatically when + another synchronization packet arrives. + [/help] +[subcommand wav] + purpose = insert a Microsoft wave header into a raw audio stream +[subcommand wmadec] + purpose = decode a wma stream diff --git a/m4/lls/gui.suite.m4 b/m4/lls/gui.suite.m4 new file mode 100644 index 00000000..e0e45efd --- /dev/null +++ b/m4/lls/gui.suite.m4 @@ -0,0 +1,58 @@ +m4_define(PROGRAM, para_gui) +m4_define(DEFAULT_CONFIG_FILE, ~/.paraslash/gui.conf) +[suite gui] +version-string = GIT_VERSION() +[supercommand para_gui] + purpose = show para_audiod status in a curses window + [description] + para_gui is a curses program which displays status information + about para_server and para_audiod in a terminal. Keys may be + mapped to user-defined commands which are executed when the key is + pressed. Command output is shown in a simple pager. + [/description] + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(config-file.m4) + m4_include(loglevel.m4) + m4_include(per-command-options-section.m4) + [option theme] + short_opt = T + summary = select startup theme + arg_info = required_arg + arg_type = string + typestr = name + [help] + If this option is not given the default theme is used. If the given + name is not a valid theme name, the list of available themes is + printed and the program terminates. + [/help] + [option stat-cmd] + short_opt = s + summary = command to read status items from + arg_info = required_arg + arg_type = string + typestr = command + default_val = para_audioc -- stat -p + [help] + On a host on which the para_audiod service is not is running, the + default command will fail. An alternative is + + para_client -- stat -p + + This command connects para_server instead of para_audiod. However, + no timing information about the current audio file is printed. + [/help] + [option key-map] + short_opt = k + summary = map a key to a command using one of three possible modes + arg_info = required_arg + arg_type = string + typestr = k:m:c + flag multiple + [help] + Mode may be d, x or p for display, external and paraslash commands, + respectively. Of course, this option may be given multiple times, + one for each key mapping. See the manual for more information. + [/help] diff --git a/m4/lls/include/channels.m4 b/m4/lls/include/channels.m4 new file mode 100644 index 00000000..ae26b735 --- /dev/null +++ b/m4/lls/include/channels.m4 @@ -0,0 +1,13 @@ +[option channels] + short_opt = c + summary = specify number of channels + typestr = num + arg_info = required_arg + arg_type = uint32 + default_val = 2 + [help] + It is only necessary to specify this option for raw audio. If it is + not given, the channel count is queried from the parent buffer tree + nodes (e.g. the decoder) or the wav header. Only if this query fails, + the default value applies. + [/help] diff --git a/m4/lls/include/color.m4 b/m4/lls/include/color.m4 new file mode 100644 index 00000000..a23b3253 --- /dev/null +++ b/m4/lls/include/color.m4 @@ -0,0 +1,29 @@ +[option color] + short_opt = C + summary = activate color output + typestr = when + arg_info = required_arg + arg_type = string + values = {COLOR_YES = "yes", COLOR_NO = "no", COLOR_AUTO = "auto"} + default_val = auto +[option log-color] + summary = select a color for one type of log message + typestr = color_spec + arg_info = required_arg + arg_type = string + flag multiple + [help] + The format of is [ []] []. + + Valid colors for and are "normal", "black", "red", "green", + "yellow", "blue", "magenta", "cyan", and "white". + + The value must be one of "bold", "dim", "ul", "blink", + "reverse". + + Examples: + + --log-color "debug:green" + --log-color "info:yellow bold" + --log-color "notice:white red bold" + [/help] diff --git a/m4/lls/include/common-option-section.m4 b/m4/lls/include/common-option-section.m4 new file mode 100644 index 00000000..2a3b3e63 --- /dev/null +++ b/m4/lls/include/common-option-section.m4 @@ -0,0 +1,8 @@ +[option common-option-section] + summary = Common options + flag ignored + [help] + The following options are implemented generically and are available + for many of the commands. + [/help] + diff --git a/m4/lls/include/complete.m4 b/m4/lls/include/complete.m4 new file mode 100644 index 00000000..c9f135c3 --- /dev/null +++ b/m4/lls/include/complete.m4 @@ -0,0 +1,8 @@ +[option complete] + summary = print possible command line completions + [help] + If this flag is given, the environment variables COMP_LINE and + COMP_POINT are examined to obtain the current command line and the + cursor position respectively. Possible completions are written to + stdout and the program exits. + [/help] diff --git a/m4/lls/include/config-file.m4 b/m4/lls/include/config-file.m4 new file mode 100644 index 00000000..a5de1336 --- /dev/null +++ b/m4/lls/include/config-file.m4 @@ -0,0 +1,14 @@ +[option config-file] + short_opt = c + summary = path to alternative config file + arg_info = required_arg + arg_type = string + typestr = filename + [help] + default: DEFAULT_CONFIG_FILE() + + PROGRAM() reads its config file right after parsing the options that + were given at the command line. If an option is given both at the + command line and in the config file, the value that was specified at + the command line takes precedence. + [/help] diff --git a/m4/lls/include/daemon.m4 b/m4/lls/include/daemon.m4 new file mode 100644 index 00000000..5c047128 --- /dev/null +++ b/m4/lls/include/daemon.m4 @@ -0,0 +1,7 @@ +[option daemon] + short_opt = d + summary = run as background daemon + [help] + If this option is given and no logfile was specified, all messages + go to /dev/null. + [/help] diff --git a/m4/lls/include/detailed-help.m4 b/m4/lls/include/detailed-help.m4 new file mode 100644 index 00000000..33a10f85 --- /dev/null +++ b/m4/lls/include/detailed-help.m4 @@ -0,0 +1,3 @@ + +[option detailed-help] + summary = print help, including all details, and exit diff --git a/m4/lls/include/group.m4 b/m4/lls/include/group.m4 new file mode 100644 index 00000000..8d49cc76 --- /dev/null +++ b/m4/lls/include/group.m4 @@ -0,0 +1,12 @@ +[option group] + short_opt = g + summary = set group id + arg_info = required_arg + arg_type = string + typestr = groupname + [help] + This option sets the group id according to . This option is + silently ignored if EUID != 0. Otherwise, real/effective GID and the + saved set-group ID are all set to the GID given by . Must not + be given in the config file. + [/help] diff --git a/m4/lls/include/help.m4 b/m4/lls/include/help.m4 new file mode 100644 index 00000000..ff6ce3eb --- /dev/null +++ b/m4/lls/include/help.m4 @@ -0,0 +1,3 @@ +[option help] + summary = print help and exit + short_opt = h diff --git a/m4/lls/include/history-file.m4 b/m4/lls/include/history-file.m4 new file mode 100644 index 00000000..deb3d7e5 --- /dev/null +++ b/m4/lls/include/history-file.m4 @@ -0,0 +1,13 @@ +[option history-file] +arg_info = required_arg +arg_type = string +typestr = filename +summary = location of the file for the command history list +[help] + If PROGRAM() runs in interactive mode, it reads the history file + on startup. Upon exit, the in-memory history is appended to the + history file. + + If this option is not given, the history file is expected at + DEFAULT_HISTORY_FILE(). +[/help] diff --git a/m4/lls/include/host.m4 b/m4/lls/include/host.m4 new file mode 100644 index 00000000..3c0f0eb4 --- /dev/null +++ b/m4/lls/include/host.m4 @@ -0,0 +1,10 @@ +[option host] + short_opt = i + summary = IP address or hostname + typestr = host + arg_info = required_arg + arg_type = string + default_val = localhost + [help] + Both IPv4 and IPv6 addresses are supported. + [/help] diff --git a/m4/lls/include/log-timing.m4 b/m4/lls/include/log-timing.m4 new file mode 100644 index 00000000..a7364e19 --- /dev/null +++ b/m4/lls/include/log-timing.m4 @@ -0,0 +1,9 @@ +[option log-timing] + short_opt = T + summary = show milliseconds in log messages + [help] + Selecting this option causes milliseconds to be included in + the log message output. This allows to measure the interval + between log messages in milliseconds which is useful for + identifying timing problems. + [/help] diff --git a/m4/lls/include/logfile.m4 b/m4/lls/include/logfile.m4 new file mode 100644 index 00000000..e3d40a14 --- /dev/null +++ b/m4/lls/include/logfile.m4 @@ -0,0 +1,10 @@ +[option logfile] +short_opt = L +arg_info = required_arg +arg_type = string +typestr = filename +summary = where to write log output +[help] + If this option is not given, PROGRAM() writes the log messages + to stderr. +[/help] diff --git a/m4/lls/include/loglevel.m4 b/m4/lls/include/loglevel.m4 new file mode 100644 index 00000000..06bea3d2 --- /dev/null +++ b/m4/lls/include/loglevel.m4 @@ -0,0 +1,23 @@ +m4_define(`downcase', `m4_translit(`$*', `A-Z', `a-z')') +m4_define(`SUITE_LOGLEVELS', `m4_patsubst(`$*', `LL_\([A-Z]+\)', + `LSGLL_\1 = "downcase(`\1')" ')') +[option loglevel] + summary = control amount of logging + short_opt = l + arg_info = required_arg + arg_type = string + typestr = severity + values = {SUITE_LOGLEVELS(LOGLEVELS())} + default_val = warning + [help] + Log only messages with severity greater or equal than the given + value. Possible values: + + debug: Produces really noisy output. + info: Still noisy, but won't fill up the disk quickly. + notice: Indicates normal, but significant event. + warning: Unexpected events that can be handled. + error: Unhandled error condition. + crit: System might be unreliable. + emerg: Last message before exit. + [/help] diff --git a/m4/lls/include/per-command-options-section.m4 b/m4/lls/include/per-command-options-section.m4 new file mode 100644 index 00000000..a303808e --- /dev/null +++ b/m4/lls/include/per-command-options-section.m4 @@ -0,0 +1,3 @@ +[option per-command-option-section] + summary = Options for PROGRAM() + flag ignored diff --git a/m4/lls/include/port.m4 b/m4/lls/include/port.m4 new file mode 100644 index 00000000..8dd63b61 --- /dev/null +++ b/m4/lls/include/port.m4 @@ -0,0 +1,7 @@ +[option port] + short_opt = p + summary = TCP port to connect to + typestr = portnumber + arg_info = required_arg + arg_type = uint32 + default_val = 8000 diff --git a/m4/lls/include/priority.m4 b/m4/lls/include/priority.m4 new file mode 100644 index 00000000..70d44b43 --- /dev/null +++ b/m4/lls/include/priority.m4 @@ -0,0 +1,13 @@ +[option priority] + summary = adjust scheduling priority + arg_info = required_arg + arg_type = int32 + typestr = prio + default_val = 0 + [help] + The priority (also known as nice value) is a value in the range -20 + to 19. Lower priorities cause more favorable scheduling. Since only + privileged processes may request a negative priority, specifying + a negative value works only if the daemon is started with root + privileges. + [/help] diff --git a/m4/lls/include/sample-format.m4 b/m4/lls/include/sample-format.m4 new file mode 100644 index 00000000..0daad192 --- /dev/null +++ b/m4/lls/include/sample-format.m4 @@ -0,0 +1,20 @@ +[option sample-format] + short_opt = f + summary = specify sample format + typestr = format + arg_info = required_arg + arg_type = string + # TODO: dedup this from enum in para.h. + values = { + SAMPLE_FORMAT_S8 = "S8", + SAMPLE_FORMAT_U8 = "U8", + SAMPLE_FORMAT_S16_LE = "S16_LE", + SAMPLE_FORMAT_S16_BE = "S16_BE", + SAMPLE_FORMAT_U16_LE = "U16_LE", + SAMPLE_FORMAT_U16_BE = "U16_BE" + } + default_val = S16_LE + [help] + It is only necessary to specify this for raw audio. See the discussion + of the --channels option. + [/help] diff --git a/m4/lls/include/sample-rate.m4 b/m4/lls/include/sample-rate.m4 new file mode 100644 index 00000000..1433521b --- /dev/null +++ b/m4/lls/include/sample-rate.m4 @@ -0,0 +1,11 @@ +[option sample-rate] + short_opt = s + summary = do not guess the input sample rate + typestr = rate + arg_info = required_arg + arg_type = uint32 + default_val = 44100 + [help] + It is only necessary to specify this for raw audio. See the discussion + of the --channels option. + [/help] diff --git a/m4/lls/include/user.m4 b/m4/lls/include/user.m4 new file mode 100644 index 00000000..6d4d31b7 --- /dev/null +++ b/m4/lls/include/user.m4 @@ -0,0 +1,21 @@ +[option user] + short_opt = u + summary = run as the given user + arg_info = required_arg + arg_type = string + typestr = username + [help] + PROGRAM() does not need any special privileges. + + If started as root (EUID == 0) this option must be given at the + command line (not in the configuration file) so that PROGRAM() + can drop the root privileges right after parsing the command line + options, but before parsing the configuration file. In this case, + real/effective/saved UID are all set to the UID of . As + the configuration file is read afterwards, those options that have + a default value depending on the UID (e.g. the directory for the + configuration file) are computed by using the uid of . This + option has no effect if PROGRAM() is started as a non-root user (i.e. + EUID != 0). + [/help] + diff --git a/m4/lls/include/version.m4 b/m4/lls/include/version.m4 new file mode 100644 index 00000000..a84ab9dd --- /dev/null +++ b/m4/lls/include/version.m4 @@ -0,0 +1,3 @@ +[option version] + summary = print version and exit + short_opt = V diff --git a/m4/lls/makefile b/m4/lls/makefile new file mode 100644 index 00000000..cf3965e6 --- /dev/null +++ b/m4/lls/makefile @@ -0,0 +1,30 @@ +.PRECIOUS: $(lls_suite_dir)/%.suite $(lsg_h) +lls_m4_include_dir := $(lls_m4_dir)/include + +$(lls_suite_dir)/%.m4d: $(lls_m4_dir)/%.suite.m4 | $(lls_suite_dir) + @[ -z "$(Q)" ] || echo 'M4D $<' + + $(Q) $(M4) -Pg -I $(lls_m4_include_dir) -s $< \ + | awk '{if ($$1 ~ /#line/) {gsub(/"/, "", $$3); if ($$3 != "$<") \ + print "$(lls_suite_dir)/$(*F).suite: " $$3}}' | sort | uniq > $@ + +$(lls_suite_dir)/%.suite: $(lls_m4_dir)/%.suite.m4 | $(lls_suite_dir) + $(Q) $(M4) -Pg -I $(lls_m4_include_dir) -D GIT_VERSION=$(GIT_VERSION) \ + -D COPYRIGHT_YEAR=$(COPYRIGHT_YEAR) -D LOGLEVELS=$(LOGLEVELS) \ + $< > $@ + +$(lls_suite_dir)/%.lsg.c: $(lls_suite_dir)/%.suite + @[ -z "$(Q)" ] || echo 'LSGC $<' + $(Q) lopsubgen --gen-c --output-dir $(lls_suite_dir) < $< + +$(lls_suite_dir)/%.lsg.h: $(lls_suite_dir)/%.suite + @[ -z "$(Q)" ] || echo 'LSGH $<' + $(Q) lopsubgen --gen-header --output-dir $(lls_suite_dir) < $< + +$(lls_suite_dir)/%.lsg.man: $(lls_suite_dir)/%.suite + @[ -z "$(Q)" ] || echo 'LSGM $<' + $(Q) lopsubgen --gen-man --output-dir $(lls_suite_dir) < $< + +$(object_dir)/%.o: $(lls_suite_dir)/%.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(STRICT_CFLAGS) $< diff --git a/m4/lls/mixer.suite.m4 b/m4/lls/mixer.suite.m4 new file mode 100644 index 00000000..37ecbd81 --- /dev/null +++ b/m4/lls/mixer.suite.m4 @@ -0,0 +1,238 @@ +m4_define(PROGRAM, para_mixer) +m4_define(DEFAULT_CONFIG_FILE, ~/.paraslash/mixer.conf) +[suite mixer] +version-string = GIT_VERSION() +caption = List of subcommands +[supercommand para_mixer] + purpose = alarm clock and volume-fader for OSS and ALSA + synopsis = [] -- [] [] + [description] + para_mixer adjusts the settings of an audio mixing device. It can + set the level of a mixer channel, or fade the level from one value + to another in a given time period. The sleep and snooze subcommands + contact para_server to start or stop streaming. + [/description] + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(config-file.m4) + m4_include(loglevel.m4) + m4_include(per-command-options-section.m4) + [help] + These options apply to several subcommands. + [/help] + [option mixer-api] + short_opt = a + summary = select alternative mixer API + arg_info = required_arg + arg_type = string + typestr = api + [help] + ALSA is preferred over OSS if both APIs are supported and this option + is not given. To see the supported mixer APIs, use this option with + an invalid string as the mixer API, e.g. --mixer-api help. + [/help] + [option mixer-device] + short_opt = m + summary = set mixer device + arg_info = required_arg + arg_type = string + typestr = device + [help] + The default device (used if this option is not given) depends + on the selected mixer API. For ALSA, the default is 'hw:0' which + corresponds to the first sound device. For OSS, '/dev/mixer' is used + as the default. + [/help] + [option mixer-channel] + short_opt = C + summary = select a mixer channel + arg_info = required_arg + arg_type = string + typestr = channel + [help] + For the ALSA mixer API, the possible values are determined at runtime + depending on the hardware and can be printed by specifying an invalid + mixer channel, for example --mixer-channel help. The default channel is + 'Master'. + + For OSS the possible values are invariably 'volume', 'bass', 'treble', + 'synth', 'pcm', 'speaker', 'line', 'mic', 'cd', 'imix', 'altpcm', + 'reclev', 'igain', 'ogain'. However, not all listed channels might be + supported on any particular hardware. The default channel is 'volume'. + [/help] + [option fade-exponent] + summary = set non-linear time scale for fading + arg_info = required_arg + arg_type = uint32 + typestr = value + default_val = 0 + [help] + This option affects the fade, snooze and sleep subcommands. It is + ignored in set mode. + + The argument must be a number between 0 and 100. The default value + 0 corresponds to linear scaling. That is, the value of the mixer + channel is increased or decreased in fixed time intervals until the + destination value is reached. Exponents between 1 and 99 cause low + channel values to be increased more quickly than high channel values. + Large exponents cause greater differences. The special value 100 sets + the destination value immediately. The command then sleeps for the + configured fade time. + [/help] +[subcommand help] + purpose = print subcommand help + non-opts-name = [] + [option long] + summary = print the long help text + short_opt = l + +[subcommand set] + purpose = set a channel to the given value and exit + [option val] + summary = mixer channel value to set + arg_info = required_arg + arg_type = uint32 + typestr = value + default_val = 10 +[subcommand fade] + purpose = fade a channel to the given value in the given time + [option fade-vol] + short_opt = f + summary = destination volume for fading + arg_info = required_arg + arg_type = uint32 + typestr = volume + default_val = 50 + [option fade-time] + short_opt = t + summary = duration of fade period + arg_info = required_arg + arg_type = uint32 + typestr = seconds + default_val = 5 +[subcommand snooze] + purpose = fade out, pause, sleep, play, fade in + [option so-time] + summary = duration of fade-out period + arg_info = required_arg + arg_type = uint32 + typestr = seconds + default_val = 30 + [option so-vol] + summary = destination volume for fade-out + arg_info = required_arg + arg_type = uint32 + typestr = volume + default_val = 20 + [option snooze-time] + summary = delay between end of fade-out and begin of fade-in + arg_info = required_arg + arg_type = uint32 + typestr = seconds + default_val = 600 + [option si-time] + summary = duration of fade-in period + arg_info = required_arg + arg_type = uint32 + typestr = seconds + default_val = 180 + [option si-vol] + summary = destination volume for fade-in + arg_info = required_arg + arg_type = uint32 + typestr = volume + default_val = 80 +[subcommand sleep] + purpose = stream, fade out, sleep, fade in + [description] + Change to the initial volume and select the initial mood/playlist. + Fade out to the given fade-out volume in the specified time. Switch + to the sleep mood/playlist and wait until wake time minus fade-in + time. Finally, switch to the wake mood/playlist and fade in to the + fade-in volume. + [/description] + [option ivol] + summary = set initial volume + arg_info = required_arg + arg_type = string + default_val = 60 + flag multiple + typestr = [channel:]volume + [help] + Used as the start volume, before fading out to the fade-out volume. The + channel part may be omitted, in which case the default channel is + used. This option may be given multiple times. + [/help] + [option fo-mood] + summary = mood or playlist for fade-out + arg_info = required_arg + arg_type = string + typestr = mood_spec + default_val = m/fade + [help] + This mood (or playlist) is selected right after setting the initial + volume. + [/help] + [option fo-time] + summary = duration of fade-out period + arg_info = required_arg + arg_type = uint32 + typestr = seconds + default_val = 1800 + [help] + No fading if this is set to 0. + [/help] + [option fo-vol] + summary = destination volume for fade-out + arg_info = required_arg + arg_type = uint32 + typestr = volume + default_val = 20 + [option sleep-mood] + summary = mood/playlist between fade-out and fade-in + arg_info = required_arg + arg_type = string + typestr = mood_spec + default_val = m/sleep + [help] + Select the given mood or playlist after the fade-out. If unset, + playback is stopped until fade-in starts. + [/help] + [option wake-time] + short_opt = w + summary = when to start fade in + arg_info = required_arg + arg_type = string + typestr = [+][HH][:MM] + default_val = +9:00 + [help] + If the optional plus character is given, the wake time is computed as + now + HH hours + MM minutes. Otherwise the HH:MM argument is considered + an absolute time (referring to either the current or the next day). + [/help] + [option fi-mood] + summary = mood or playlist for fade-in + arg_info = required_arg + arg_type = string + typestr = mood_spec + default_val = m/wake + [help] + This mood or playlist is selected right before fade-in begins. + [/help] + [option fi-time] + summary = duration of fade-in period + arg_info = required_arg + arg_type = uint32 + typestr = seconds + default_val = 1200 + [help] + No fading if this is set to 0. + [/help] + [option fi-vol] + summary = destination volume for fade-in + arg_info = required_arg + arg_type = uint32 + typestr = volume + default_val = 80 diff --git a/m4/lls/play.suite.m4 b/m4/lls/play.suite.m4 new file mode 100644 index 00000000..0fbba0c0 --- /dev/null +++ b/m4/lls/play.suite.m4 @@ -0,0 +1,40 @@ +m4_define(PROGRAM, para_play) +m4_define(DEFAULT_HISTORY_FILE, ~/.paraslash/play.history) +m4_define(DEFAULT_CONFIG_FILE,~/.paraslash/play.conf) +[suite play] +version-string = GIT_VERSION() +[supercommand para_play] + purpose = command line audio player + non-opts-name = ... + [description] + para_play operates either in command mode or in insert mode. In + insert mode it presents a prompt and allows to enter commands like + stop, play, pause etc. In command mode the current audio file and the + playback position are shown and the program reads single key strokes + from stdin. Keys may be mapped to commands so that the configured + command is executed when a mapped key is pressed. + [/description] + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(loglevel.m4) + m4_include(config-file.m4) + m4_include(history-file.m4) + m4_include(per-command-options-section.m4) + [option randomize] + short_opt = z + summary = randomize playlist at startup + [option key-map] + short_opt = k + summary = map a key to a command + arg_info = required_arg + arg_type = string + flag multiple + typestr = key:command [args] + [help] + This option may be given multiple times, one for each key + mapping. Example: + + --key-map 5:jmp 50 + [/help] diff --git a/m4/lls/play_cmd.suite.m4 b/m4/lls/play_cmd.suite.m4 new file mode 100644 index 00000000..195b47b9 --- /dev/null +++ b/m4/lls/play_cmd.suite.m4 @@ -0,0 +1,92 @@ +[suite play_cmd] +caption = list of commands +[subcommand help] + purpose = list commands or keybindings, or print command-specific help + non-opts-name = [command] + [description] + + This command acts differently depending on whether it is executed in + command mode or in insert mode. + + In command mode, the help command prints the list of defined + keybindings. In insert mode, if no argument is given, it prints the + list of available commands. When called with the name of a command + as first argument, it prints the description of this command. + [/description] + +[subcommand fg] + purpose = enter command mode + [description] + In this mode, file name and play time of the current audio file are + displayed. Hit CTRL+C or : to switch to input mode. + [/description] +[subcommand next] + purpose = load the next file of the playlist + +[subcommand prev] + purpose = load the previous file of the playlist + +[subcommand bg] + purpose = enter insert mode + [description] + Only useful if called in command mode via a key binding. The default + key bindings map the colon key to this command, so pressing : in + command mode activates insert mode. + [/description] + +[subcommand jmp] + purpose = change playback position + non-opts-name = percent + [description] + The percent argument must be an integer between 0 and 100, inclusively. + [/description] + +[subcommand ff] + purpose = set playback position relative to the current position + non-opts-name = seconds + [description] + Negative values mean to jump backwards the given amount of seconds. + [/description] + +[subcommand ls] + purpose = list the playlist + [description] + This prints all paths of the playlist. The currently + active file is marked with an asterisk. + [/description] + +[subcommand info] + purpose = print information about the current file + [description] + The output contains the playlist position, the path + and information provided by the audio format handler. + [/description] + +[subcommand play] + purpose = start or resume playback + non-opts-name = [num] + [description] + If no argument is given, playback starts at the current + position. Otherwise, the corresponding file is loaded + and playback is started at the beginning of the file. + [/description] + +[subcommand pause] + purpose = suspend playback + [description] + When paused, it is still possible to jump around in + the file via the jmp and ff commands. + [/description] + +[subcommand tasks] + purpose = print list of active tasks + [description] + Mainly useful for debugging. + [/description] + +[subcommand quit] + purpose = exit para_play + [description] + Pressing CTRL+D causes EOF on stdin which also exits + para_play. + [/description] diff --git a/m4/lls/recv.suite.m4 b/m4/lls/recv.suite.m4 new file mode 100644 index 00000000..793a2f5f --- /dev/null +++ b/m4/lls/recv.suite.m4 @@ -0,0 +1,22 @@ +m4_define(PROGRAM, para_recv) +[suite recv] +version-string = GIT_VERSION() +[supercommand para_recv] + purpose = a command line HTTP/DCCP/UDP stream grabber + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(loglevel.m4) + m4_include(per-command-options-section.m4) + [option receiver] + short_opt = r + summary = select the receiver + arg_info = required_arg + arg_type = string + typestr = receiver_spec + [help] + Any options for the selected receiver must be quoted. Example: + + -r 'http -i www.paraslash.org -p 8009' + [/help] diff --git a/m4/lls/recv_cmd.suite.m4 b/m4/lls/recv_cmd.suite.m4 new file mode 100644 index 00000000..674a4487 --- /dev/null +++ b/m4/lls/recv_cmd.suite.m4 @@ -0,0 +1,102 @@ +[suite recv_cmd] +caption = receivers +[subcommand afh] + purpose = make an audio stream from a local file + [description] + The afh (audio format handler) receiver extracts selected parts of + the given audio file without decoding the file. Only complete chunks + with respect to the underlying audio format are extracted. + [/description] + [option filename] + short_opt = f + summary = file to open + typestr = filename + arg_info = required_arg + arg_type = string + [option begin-chunk] + short_opt = b + summary = skip the beginning of the file + typestr = chunk_num + arg_info = required_arg + arg_type = int32 + [help] + The argument must be an integer between -num_chunks and num_chunks - + 1, inclusively, where num_chunks is the total number of chunks. If + chunk_num is negative, the given number of chunks are counted backwards + from the end of the file. For example --begin-chunk -100 instructs + the afh receiver to start at chunk num_chunks - 100. This is useful + for cutting off the beginning of an audio file. + [/help] + [option end-chunk] + short_opt = e + summary = only write up to chunk chunk_num + typestr = chunk_num + arg_info = required_arg + arg_type = int32 + [help] + For the chunk_num argument the same rules as for --begin-chunk + apply. The default is to write up to the last chunk. + [/help] + [option just-in-time] + short_opt = j + summary = use timed writes + [help] + Write the specified data chunks 'just in time', i.e., delay the write + until data is needed by the decoder/player for an uninterrupted audio + stream. This may be useful for third-party software. + [/help] + [option no-header] + short_opt = h + summary = do not write an audio file header + [help] + Some audio formats store information about the audio file in + a format-specific header which is needed to decode any part of + the file. For such formats the afh receiver generates a suitable + header. This option changes the default behaviour, i.e. no header + is written. + [/help] +[subcommand http] + purpose = receive an audio stream over HTTP + m4_include(host.m4) + m4_include(port.m4) +[subcommand dccp] + purpose = receive an audio stream over DCCP + m4_include(host.m4) + m4_include(port.m4) + [option ccid] + short_opt = c + summary = CCID preference(s) for this connection + typestr = id + arg_info = required_arg + arg_type = uint32 + flag multiple + [help] + When present exactly once, this option mandates the CCID for the + sender-receiver connection. If it is passed more than once, it sets + a preference list where the order of appearance signifies descending + priority. For example, passing 4, 2, 3 creates the preference list + (CCID-4, CCID-2, CCID-3), assigning CCID-4 highest preference. + + The request is reconciled with the CCIDs on the server through the + 'server-priority' mechanism of RFC 4340 6.3.1/10. The server CCIDs + can be listed by calling 'para_client si'. + [/help] +[subcommand udp] + purpose = receive an audio stream over UDP + [option host] + short_opt = i + summary = IP address or hostname + typestr = host + arg_info = required_arg + arg_type = string + default_val = 224.0.1.38 + [help] + The default address resolves to DANTZ.MCAST.NET and activates + multicast. + [/help] + m4_include(port.m4) + [option iface] + summary = receiving udp multicast interface + typestr = iface-name + arg_info = required_arg + arg_type = string diff --git a/m4/lls/server.suite.m4 b/m4/lls/server.suite.m4 new file mode 100644 index 00000000..5bba85d4 --- /dev/null +++ b/m4/lls/server.suite.m4 @@ -0,0 +1,358 @@ +m4_define(PROGRAM, para_server) +m4_define(DEFAULT_CONFIG_FILE, ~/.paraslash/server.conf) +[suite server] +version-string = GIT_VERSION() +[supercommand para_server] + purpose = manage and stream audio files + [description] + para_server streams audio files over a local or remote network. It + is controlled by para_client(1), which connects para_server through + the paraslash control service. + + On startup the server spawns a second process, the audio file selector, + which maintains the database of all known audio files. This database + contains file format, duration and tag information of each known file + and statistics such as last-played time, and the number of times each + file was streamed. Lyrics and cover art may be added to the database + and associated with one or more audio files. + + Besides ordinary playlists the audio file selector supports so-called + moods. Moods instruct the server to determine the files to be streamed + and their order in terms of properties stored in the database. + [/description] + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(config-file.m4) + m4_include(priority.m4) + m4_include(daemon.m4) + m4_include(logfile.m4) + m4_include(user.m4) + m4_include(group.m4) + m4_include(loglevel.m4) + m4_include(log-timing.m4) + m4_include(color.m4) + m4_include(per-command-options-section.m4) + [option port] + short_opt = p + summary = listening port of the paraslash control service + arg_info = required_arg + arg_type = uint32 + typestr = portnumber + default_val = 2990 + [help] + para_server listens on this TCP port for incoming connections + from clients such as para_client. If the default port is changed, + the corresponding option of para_client must be used to connect + to para_server. + [/help] + [option user-list] + summary = file which contains user names and credentials + arg_info = required_arg + arg_type = string + typestr = path + [help] + This file contains one line per user of the form + + user + + See the manual for more information. + [/help] + [option vss] + summary = Options for the virtual streaming system + flag ignored + [option autoplay] + summary = start streaming on startup + short_opt = a + [help] + The default is to defer streaming until para_client connects and + executes the "play" command. + [/help] + [option autoplay-delay] + summary = time to wait before streaming + arg_info = required_arg + arg_type = uint32 + typestr = milliseconds + default_val = 0 + [help] + This option is ignored if --autplay is not given. Otherwise, its + argument defines for how long streaming is delayed at startup. + + This is useful in init scripts to set the delay large enough to make + sure para_audiod is up when para_server starts to stream. + [/help] + [option announce-time] + short_opt = A + summary = grace time for data connections + arg_info = required_arg + arg_type = uint32 + typestr = milliseconds + default_val = 300 + [help] + para_server tells para_audiod through the control service connection + whether an audio stream is currently available. This option defines + the delay between announcing the stream and sending the first chunk + of audio data. + [/help] + [option afs] + summary = Options for the audio file selector + flag ignored + [option afs-database-dir] + summary = location of the afs database + short_opt = D + arg_info = required_arg + arg_type = string + typestr = directory + [help] + The directory which contains the database for the audio file + selector. The default is ~/.paraslash/afs_database-0.4. + + If no database was found, the "init" command must be executed to + initialize the database. Once initialized, audio files may added with + the "add" command. + [/help] + [option afs-socket] + summary = socket for afs connections + short_opt = s + arg_info = required_arg + arg_type = string + typestr = path + default_val = /var/paraslash/afs_command_socket-0.4 + [help] + Server commands communicate with the audio file selector, via a + local socket. This option specifies the location of the socket in + the file system. + [/help] + [option afs-initial-mode] + summary = mood or playlist to load on startup + short_opt = i + arg_info = required_arg + arg_type = string + typestr = specifier/name + [help] + The argument of this option consists of a prefix, either 'm/' or + 'p/', to indicate whether a mood or a playlist should be loaded, + followed by the name of the mood or playlist. Example: + + --afs-initial-mode p/foo + + loads the playlist named "foo". + + If this option is not given, the dummy mood is loaded at startup. + [/help] + [option http] + summary = Options for the http sender + flag ignored + [option http-port] + summary = TCP port for http streaming + arg_info = required_arg + arg_type = uint32 + typestr = portnumber + default_val = 8000 + [help] + The http sender of para_server listens on this port for incoming + connections. Clients are expected to send the usual http request + message such as 'GET / HTTP/'. + [/help] + [option http-default-deny] + summary = make the http access control list a whitelist + [help] + The default is to use blacklists, i.e. connections to the http sender + are allowed unless the connecting host matches a pattern given by a + http-access option. This allows to use access control the other way + round: Connections are denied from hosts which are not explicitly + allowed by one or more http-access options. + [/help] + [option http-access] + summary = add an entry to the http access control list + arg_info = required_arg + arg_type = string + typestr = a.b.c.d/n + flag multiple + [help] + Add the given host/network to access control list (whitelist if + http-default-deny was given, blacklist otherwise) before opening + the tcp port. This option can be given multiple times. Example: + + --http-access 192.168.0.0/24 + + whitelists/blacklists the 256 hosts 192.168.0.x. + + This option may be given multiple times to blacklist/whitelist any + number of hosts or networks. + [/help] + [option http-no-autostart] + summary = do not open TCP port for http streaming on startup + [help] + If this option is given, the http sender does not listen on its TCP + port until the "sender" command is executed to open the port. + [/help] + [option http-max-clients] + summary = maximal number of simultaneous http connections + arg_info = required_arg + arg_type = int32 + typestr = number + default_val = -1 + [help] + The http sender will refuse connections if already that number of + clients are currently connected. A non-positive value (the default) + allows for an unlimited number of simultaneous connections. + [/help] + [option dccp] + summary = Options for the dccp sender + flag ignored + [option dccp-port] + summary = port for dccp streaming + arg_info = required_arg + arg_type = uint32 + typestr = portnumber + default_val = 8000 + [help] + See --http-port for details. + [/help] + [option dccp-default-deny] + summary = make the dccp access control list a whitelist + [help] + See http-default-deny for details. + [/help] + [option dccp-access] + summary = add an entry to the dccp access control list + arg_info = required_arg + arg_type = string + typestr = a.b.c.d/n + flag multiple + [help] + See --http-access for details. + [/help] + [option dccp-max-clients] + summary = maximal number of simultaneous dccp connections + arg_info = required_arg + arg_type = int32 + typestr = number + default_val = -1 + [help] + See --http-max-clients for details. + [/help] + [option dccp-max-slice-size] + summary = upper bound for the FEC slice size + arg_info = required_arg + arg_type = uint32 + typestr = bytes + default_val = 0 + [help] + If this value is zero (the default) the dccp sender uses the maximum + packet size (MPS) of the connection as the slice size. The MPS is a + network parameter and depends on the path maximum transmission unit + (path MTU) of an incoming connection, i.e. on the largest packet size + that can be transmitted without causing fragmentation. + + This option allows to use a value less than the MPS in order to + fine-tune application performance. Values greater than the MPS of an + incoming connection can not be set. + [/help] + [option dccp-data-slices-per-group] + summary = the number of non-redundant slices per FEC group + arg_info = required_arg + arg_type = uint32 + typestr = count + default_val = 3 + [help] + This determines the number of slices in each FEC group which are + necessary to decode the group. The given number must be smaller than + the argument to the --dccp-slices-per-group option below. + + Note that the duration of a FEC group is proportional to the + product dccp-max-slice-size * dccp-data-slices-per-group. + [/help] + [option dccp-slices-per-group] + summary = the total number of slices per FEC group + arg_info = required_arg + arg_type = uint32 + typestr = count + default_val = 4 + [help] + This value must be larger than the value of the argument to + --dccp-data-slices-per-group. The difference of the two values is + the number of redundant slices, that is, the number of slices which + may be lost without causing interruptions in the audio stream. + + Increase this value if you are on a lossy network. + [/help] + [option udp] + summary = Options for the udp sender + flag ignored + [option udp-target] + summary = add udp target with optional port + arg_info = required_arg + arg_type = string + typestr = host[:port] + flag multiple + [help] + Add the given host/port to the list of targets. The "host" argument + can be either an IPv4/v6 address or hostname (RFC 3986 syntax). The + "port" argument is an optional port number. If the "port" part is + absent, the "--udp-default-port" value (see below) is used. + + The following examples are possible targets: "10.10.1.2:8000" + (host:port); "10.10.1.2" (with default port); "224.0.1.38:1500" + (IPv4 multicast); "localhost:8001" (hostname with port); "[::1]:8001" + (IPv6 localhost); "[badc0de::1]" (IPv6 host with default port); + "[FF00::beef]:1500" (IPv6 multicast). + + This option can be given multiple times, for multiple targets. + [/help] + [option udp-default-port] + summary = default port for udp targets + arg_info = required_arg + arg_type = uint32 + typestr = portnumber + default_val = 8000 + [option udp-no-autostart] + summary = do not send the audio stream to UDP targets + [help] + If this option is given, udp streaming may be activated at a later + time by executing the "sender" command. + [/help] + [option udp-mcast-iface] + summary = outgoing udp multicast interface + arg_info = required_arg + arg_type = string + typestr = interface + [option udp-header-interval] + short_opt = H + summary = duration for sending header + arg_info = required_arg + arg_type = uint32 + typestr = milliseconds + default_val = 2000 + [help] + As the udp sender has no idea about connected clients it sends the + audio file header periodically if necessary. This option specifies the + duration between subsequent headers are sent. Smaller values decrease + the average time clients have to wait before starting playback, + larger values decrease network traffic. + + Note that this affects only ogg/* and wma streams. Other audio formats, + including mp3, don't need an audio file header. + [/help] + [option udp-ttl] + short_opt = t + summary = set time to live value + arg_info = required_arg + arg_type = int32 + typestr = num + default_val = -1 + [help] + This option applies exclusively to multicast UDPv4/v6 streaming. + + For the sending UDPv4 socket it sets the multicast Time-To-Live value + to "num". Traditional TTL scope values are: 0=host, 1=network, 32=same + site, 64=same region, 128=same continent, 255=unrestricted. Please + note however that this scoping is not a good solution: RFC 2365 + e.g. presents a better alternative. + + When using UDPv6 multicasting, the option sets the number of multicast + hops (as described in RFC 3493); a value of -1 allows the kernel to + auto-select the hop value. + [/help] diff --git a/m4/lls/server_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4 new file mode 100644 index 00000000..e2bd162d --- /dev/null +++ b/m4/lls/server_cmd.suite.m4 @@ -0,0 +1,680 @@ +[suite server_cmd] +caption = list of server commands +aux_info_prefix = Permissions: + +[introduction] + The server process listens on a network socket and accepts connections + from para_client or para_audiod. For the connection to succeed the + connecting peer must authenticate as one of the users stored in the + user table of para_server. Each entry of the user table contains the + set of permission bits that are granted to the user. Authenticated + users may execute one of the commands below if the set of permission + bits of the command is a subset of the permission bits that are + granted to the user. +[/introduction] + +[subcommand add] + purpose = add or update audio files + non-opts-name = path... + aux_info = AFS_READ | AFS_WRITE + [description] + Each path must be absolute and refer to either an audio file or a + directory. In case of a directory, all audio files in that directory + are added recursively. Note that the given paths refer to files or + directories on the host on which para_server is running. + [/description] + [option all] + short_opt = a + summary = add all files + [help] + The default is to add only files ending in a known suffix for a + supported audio format. + [/help] + [option lazy] + short_opt = l + summary = add files lazily + [help] + If the path already exists in the database, skip this file. This + operation is really cheap. Useful to update large directories after + some files have been added. + [/help] + [option force] + short_opt = f + summary = force adding/updating + [help] + Recompute the audio format handler data even if a file with the same + path and the same hash value exists. + [/help] + [option verbose] + short_opt = v + summary = enable verbose mode + [help] + Print what is being done. + [/help] + +[subcommand addatt] + purpose = add new attribute(s) + non-opts-name = attribute... + aux_info = AFS_READ | AFS_WRITE + [description] + This adds new attributes to the attribute table. At most 64 attributes + may be defined. + [/description] + +[subcommand check] + purpose = run integrity checks on database tables + aux_info = AFS_READ + [description] + If no options are given, all checks are run. + [/description] + [option aft] + short_opt = a + summary = run audio file table checks + [help] + Report stale paths and invalid image and lyrics ids of the audio + file table. + [/help] + [option attribute] + short_opt = A + summary = check for invalid attributes + [help] + Report audio files whose attribute bitmask is invalid, i.e., has a bit + set which does not correspond to any attribute of the attribute table. + [/help] + [option mood] + short_opt = m + summary = check for invalid mood definitions + [help] + Run syntax checks on all moods of the mood table. + [/help] + [option playlist] + short_opt = p + summary = find invalid paths in playlists + [help] + Check all playlists for paths not contained in the audio file table. + [/help] + +[subcommand cpsi] + purpose = copy selected parts of the audio file selector info + non-opts-name = source pattern... + aux_info = AFS_READ | AFS_WRITE + [description] + If no option, or only --verbose is given, all fields of the audio + file selector info structure are copied to each row of the audio file + table whose path matches at least one of the given patterns. Otherwise, + only those fields which correspond to the given options are copied. + [/description] + [option attribute-bitmap] + short_opt = a + summary = copy the attribute bitmap + [option image-id] + short_opt = i + summary = copy the image id + [option lyrics-id] + short_opt = y + summary = copy the lyrics id + [option lastplayed] + short_opt = l + summary = copy the lastplayed timestamp + [option numplayed] + short_opt = n + summary = copy the numplayed counter + [option verbose] + short_opt = v + summary = enable verbose mode + +[subcommand ff] + purpose = jump N seconds forward or backward + synopsis = n[-] + aux_info = VSS_READ | VSS_WRITE + [description] + This sets the 'R' (reposition request) bit of the vss status flags + which enqueues a request to jump n seconds forwards or backwards. + + Example: + + para_client ff 30- + + jumps 30 seconds backwards. + + [/description] + +[subcommand help] + purpose = list available commands or print command-specific help + non-opts-name = [command] + aux_info = NO_PERMISSION_REQUIRED + [description] + Without any arguments, help prints a list of available commands. When + called with a command name as first argument, it prints the description + of this command. + [/description] + +[subcommand hup] + purpose = reload config file, log file and user list + aux_info = VSS_WRITE + [description] + Reread the config file and the user list file, close and reopen the log + file, and ask the afs process to do the same. Sending the HUP signal + to the server process has the same effect as running this command. + [/description] + +[subcommand init] + purpose = initialize the database tables for the audio file selector + synopsis = [table_name...] + aux_info = AFS_READ | AFS_WRITE + [description] + When invoked without arguments, this command creates all + tables: audio_files, attributes, scores, moods, lyrics, images, + playlists. Otherwise only the given tables are created. + [/description] + +[subcommand jmp] + purpose = reposition the current stream + non-opts-name = n + aux_info = VSS_READ | VSS_WRITE + [description] + Set the 'R' (reposition request) bit of the vss status flags and + enqueue a request to jump to n% of the current audio file, where 0 <= + n <= 100. + [/description] + +[subcommand ls] + purpose = list audio files which match a pattern + non-opts-name = [pattern...] + aux_info = AFS_READ + [description] + If no pattern is given, all files are listed. Otherwise, the command + lists all files of the audio file table whose path matches at least + one of the given patterns. + [/description] + [option listing-mode] + short_opt = l + summary = use alternative output format + arg_type = string + arg_info = optional_arg + typestr = mode + default_val = long + [help] + The optional mode argument is either a single character or a word + according to the following list. + + short (s). List only the path or basename (last component of the path), + depending on whether -p is also given. This listing mode acts as if + --listing-mode had not been given. + + long (l). Show detailed information. This is the default if no argument + to --listing-mode is supplied. + + verbose (v). Multi-line output, one row per data field stored in the + audio file table. + + parser-friendly (p). Like verbose listing mode, but use numerical + values for the names of the output fields and prefix each line with + a length field. + + mbox (m). Generate output suitable to be viewed with a mail + program. One "mail" per matching audio file. + + chunk-table (c). Print path (or basename, depending on whether -p is + also given), chunk time and chunk offsets. + + [/help] + [option full-path] + short_opt = F + summary = list full paths, match full paths against patterns + [help] + This option is the default, so it does nothing. Deprecated as of + v0.6.0, scheduled for removal in v0.6.1. + [/help] + [option basename] + short_opt = b + summary = list and match basenames only + [help] + Print only the basename of each matching file and match only the + basenames of the paths stored in the audio file table against the + given patterns. The default is to print and match the full path. + [/help] + [option admissible] + short_opt = a + summary = list only admissible files + [help] + List only files which are admissible with respect to the current mood + or playlist. + [/help] + [option reverse] + short_opt = r + summary = reverse sort order + [option unix-date] + short_opt = d + summary = print dates as seconds after the epoch + [option sort] + short_opt = s + summary = change sort order + arg_type = string + arg_info = required_arg + typestr = order + default_val = path + [help] + The sort order must be given as an required argument. Like for + --listing-mode, this argument may either be a single character or a + word, according to the following list. + + path (p). Sort alphabetically by path or basename, depending on + whether -b is given. This is the default if --sort is not given. + + score (s). Iterate over the entries of the score table, rather than + the audio file table. This sort order implies --admissible, since + the score table contains only admissible files. + + lastplayed (l) + + numplayed (n) + + frequency (f) + + channels (c) + + image-id (i) + + lyrics-id (y) + + bitrate (b) + + duration (d) + + audio-format (a) + + If --sort is not given, path sort is implied. + [/help] + +[subcommand lsatt] + purpose = list attributes + aux_info = AFS_READ + [description] + Print the list of all defined attributes which match the given + pattern. If no pattern is given, the full list is printed. + [/description] + + [option id-sort] + short_opt = i + summary = sort attributes by id + [help] + The default is to sort alphabetically by name. + + Attributes are internally represented as an 64 bit array. The attribute + id is the bit number in this array. + [/help] + [option long] + short_opt = l + summary = print long listing + [help] + The long listing prints the attribute id in addition to the name of + the attribute. The id is printed as a decimal number and is separated + from the name by a tab character. + [/help] + [option reverse] + short_opt = r + summary = reverse sort order + +[subcommand mvatt] + purpose = rename an attribute + synopsis = source dest + aux_info = AFS_READ | AFS_WRITE + [description] + Rename the attribute given by the first argument to the destination + given by the second argument. It is an error if the destination + attribute exists. + [/description] + +[subcommand next] + purpose = close the stream and start to stream the next audio file + aux_info = VSS_READ | VSS_WRITE + [description] + Set the 'N' (next audio file) bit of the vss status flags. This + instructs the server to close the current stream, if any. The 'P' + (playing) bit is not modified by this command. If it is on, playing + continues with the next audio file. + + This command is equivalent to stop if paused, and has no effect + if stopped. + [/description] + +[subcommand nomore] + purpose = stop playing after current audio file + aux_info = VSS_READ | VSS_WRITE + [description] + Set the 'O' (no more) bit of the vss status flags which asks + para_server to clear the 'P' (playing) bit after the 'N' (next audio + file) bit transitions from off to on (because the end of the current + audio file is reached). Use this command instead of stop if you don't + like sudden endings. + [/description] + +[subcommand pause] + purpose = suspend the current stream + aux_info = VSS_READ | VSS_WRITE + [description] + Clear the 'P' (playing) bit of the vss status flags. + [/description] + +[subcommand play] + purpose = start or resume playback + aux_info = VSS_READ | VSS_WRITE + [description] + Set the 'P' (playing) bit of the vss status flags. + [/description] + +[subcommand rm] + purpose = remove rows from the audio file table + non-opts-name = pattern... + aux_info = AFS_READ | AFS_WRITE + [description] + Remove all rows of the audio file table which match any of the given + patterns. Note that this affects only the database table; the command + won't touch your audio files on disk. + [/description] + [option verbose] + short_opt = v + summary = print paths of deleted rows + [option force] + short_opt = f + summary = don't complain if nothing was removed + [option pathname-match] + short_opt = p + summary = modify matching behaviour + [help] + Match a slash in the path only with a slash in pattern and not by an + asterisk (*) or a question mark (?) metacharacter, nor by a bracket + expression ([]) containing a slash (see fnmatch(3)). + [/help] + +[subcommand rmatt] + purpose = remove attribute(s) + non-opts-name = pattern... + aux_info = AFS_READ | AFS_WRITE + [description] + Remove all attributes which match any given pattern. All information + about the removed attributes in the audio file table is lost. + [/description] + +[subcommand select] + purpose = activate a mood or a playlist + non-opts-name = specifier/name + aux_info = AFS_READ | AFS_WRITE + [description] + The specifier is either 'm' or 'p' to indicate whether a playlist or + a mood should be activated. Example: + + select m/foo + + activates the mood named 'foo'. + [/description] + +[subcommand sender] + purpose = control paraslash senders + synopsis = [sender cmd [arguments]] + aux_info = VSS_READ | VSS_WRITE + [description] + Send a command to a specific sender. The following commands are + available, but not all senders support every command. + + help, on, off, add, delete, allow, deny, status. + + The help command prints the help text of the given sender. If no + command is given the list of available senders is shown. + + Example: + + para_client sender http help + + [/description] + +[subcommand setatt] + purpose = set or unset attributes + synopsis = attribute{+|-}... pattern... + aux_info = AFS_READ | AFS_WRITE + [description] + Set ('+') or unset ('-') the given attributes for all audio files + matching the given pattern. Example: + + setatt rock+ punk+ pop- '*foo.mp3' + + sets the 'rock' and the 'punk' attribute and unsets the 'pop' attribute + of all files ending with 'foo.mp3'. + [/description] + +[subcommand si] + purpose = print server info + aux_info = NO_PERMISSION_REQUIRED + [description] + Show server and afs PID, number of connections, uptime and more. + [/description] + +[subcommand stat] + purpose = print information about the current audio file + aux_info = VSS_READ + [option num] + short_opt = n + summary = number of times to show the status info + arg_info = required_arg + arg_type = uint32 + typestr = num + [help] + Exit after the status information has been shown num times. If this + option is not given, the command runs in an endless loop. + [/help] + [option parser-friendly] + short_opt = p + summary = enable parser-friendly output + [help] + Show status item identifiers as numerical values and prefix each + status item with its size in bytes. + [/help] + +[subcommand stop] + purpose = stop playback + aux_info = VSS_READ | VSS_WRITE + [description] + Clear the 'P' (playing) bit and set the 'N' (next audio file) bit of + the vss status flags, effectively stopping playback. + [/description] + +[subcommand tasks] + purpose = list active server tasks + aux_info = NO_PERMISSION_REQUIRED + [description] + For each task, print ID, status and name. This is mostly useful + for debugging. + [/description] + +[subcommand term] + purpose = ask the server to terminate + aux_info = VSS_READ | VSS_WRITE + [description] + Shut down the server. Instead of this command, you can also send + SIGINT or SIGTERM to the para_server process. It should never be + necessary to send SIGKILL. + [/description] + +[subcommand touch] + purpose = manipulate the afs information of audio files + non-opts-name = pattern... + aux_info = AFS_READ | AFS_WRITE + [description] + This command modifies the afs info structure of all rows of the audio + file table whose path matches at least one of the given patters. + + If at least one option is given which takes a number as its argument, + only those fields of the afs info structure are updated which + correspond to the given options while all other fields stay unmodified. + + If no such option is given, the lastplayed field is set to the current + time and the value of the numplayed field is increased by one while + all other fields are left unchanged. This mimics what happens when + the virtual streaming system selects the file for streaming. + + If the file is admissible for the current mood (or contained in the + current playlist), its score is recomputed according to the changed + values. + [/description] + [option numplayed] + short_opt = n + summary = set the numplayed count manually + arg_type = uint32 + arg_info = required_arg + typestr = num + [help] + The numplayed count of an audio file is the number of times the file + was selected for streaming. It is one of the inputs to the scoring + function which determines the order in which admissible files are + streamed. + + The virtual streaming system increases this number automatically each + time it opens the file for streaming. + [/help] + [option lastplayed] + short_opt = l + summary = set the lastplayed time manually + arg_type = uint64 + arg_info = required_arg + typestr = num + [help] + The lastplayed time of an audio file is the time when the file was + last opened for streaming. + + Like the numplayed count, it is an input for the scoring function + and is updated automatically by the virtual streaming system. + + The argument must be a number of seconds since the epoch. Example: + + touch -l=$(date +%s) file + + sets the lastplayed time of 'file' to the current time. + [/help] + [option image-id] + short_opt = i + summary = set the image id + arg_type = uint32 + arg_info = required_arg + typestr = num + [help] + The afs info structure of each row of the audio file table contains + a slot for the image id of the audio file that corresponds to the + row. The image id stored in this slot refers to the key in the image + table that identifies the blob. + + When a new audio file is added to the audio file table, its image + id starts out as zero, indicating that there is no image associated + with the file. Setting the image id to a non-zero number associates + the file with a particular blob of the image table, for example the + cover art of the album in jpg format. + [/help] + [option lyrics-id] + short_opt = y + summary = set the lyrics id + arg_type = uint32 + arg_info = required_arg + typestr = num + [help] + This option works just like --image-id, but sets the lyrics ID rather + than the image id. + [/help] + [option amp] + short_opt = a + summary = set the amplification value (0-255) + arg_type = uint32 + arg_info = required_arg + typestr = num + [help] + The amplification value of an audio file is a number which is stored + in the afs info structure. + + The value determines the scaling factor by which the amplitude of + the decoded samples should be multiplied in order to normalize the + volume. A value of zero means no amplification, 64 means the amplitude + should be multiplied by a factor of two, 128 by three and so on. + + The amp filter of para_audiod amplifies the volume according to + this value. + [/help] + [option verbose] + short_opt = v + summary = explain what is being done + [option pathname-match] + short_opt = p + summary = modify matching behaviour + [help] + Match a slash in the path only with a slash in pattern and not by an + asterisk (*) or a question mark (?) metacharacter, nor by a bracket + expression ([]) containing a slash (see fnmatch(3)). + [/help] + +[subcommand version] + purpose = print the git version string of para_server + aux_info = NO_PERMISSION_REQUIRED + [option verbose] + short_opt = v + summary = print detailed (multi-line) version text + +m4_define(`BLOB_COMMANDS', ` +[subcommand rm`$2'] + purpose = remove `$1' blob(s) + non-opts-name = pattern... + aux_info = AFS_READ | AFS_WRITE + [description] + Remove all `$1' blobs which match any of the given patterns. + [/description] + +[subcommand mv`$2'] + purpose = rename `$1' blob(s) + non-opts-name = source dest + aux_info = AFS_READ | AFS_WRITE + [description] + Rename `$1' source to dest. The command fails if the source `$1' + does not exist or if the destination `$1' already exists. + [/description] + +[subcommand add`$2'] + purpose = add a blob to the `$1' table + non-opts-name = `$1'_name + aux_info = AFS_READ | AFS_WRITE + [description] + Read from stdin and ask the audio file selector to create a blob in + the `$1' table. If the named blob already exists, it gets replaced + with the new data. + [/description] + +[subcommand cat`$2'] + purpose = dump a `$1' blob to stdout + non-opts-name = `$1'_name + aux_info = AFS_READ + +[subcommand ls`$2'] + purpose = list blobs of type `$1' which match a pattern + non-opts-name = [pattern...] + aux_info = AFS_READ + [description] + Print the list of all blobs which match the given pattern. If no + pattern is given, the full list is printed. + [/description] + [option id-sort] + short_opt = i + summary = sort by identifier + [help] + The default is to sort alphabetically by name. + [/help] + [option long] + short_opt = l + summary = long listing + [help] + Print identifier and name. The default is to print only the name. + [/help] + [option reverse] + short_opt = r + summary = reverse sort order +') + +BLOB_COMMANDS(`moods', `mood') +BLOB_COMMANDS(`playlist', `pl') +BLOB_COMMANDS(`image', `img') +BLOB_COMMANDS(`lyrics', `lyr') diff --git a/m4/lls/write.suite.m4 b/m4/lls/write.suite.m4 new file mode 100644 index 00000000..a2f50dfa --- /dev/null +++ b/m4/lls/write.suite.m4 @@ -0,0 +1,35 @@ +m4_define(PROGRAM, para_write) +[suite write] +version-string = GIT_VERSION() +[supercommand para_write] + purpose = play wav or raw audio + [description] + para_write reads audio data from stdin and starts one supported writer. + [/description] + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(loglevel.m4) + m4_include(per-command-options-section.m4) + [option writer] + short_opt = w + summary = select writer to start + arg_info = required_arg + arg_type = string + flag multiple + typestr = 'name [options]' + [help] + May be given multiple times, and the same writer may be specified more + than once. If this option is not given, the first supported writer + is started. The list of supported writers is shown in the help output. + + Options for a particular writer may be specified for each given + '--writer' option separately. You will have to quote these options + like this: + + --writer 'oss --device /dev/dsp2' + [/help] + m4_include(channels.m4) + m4_include(sample-rate.m4) + m4_include(sample-format.m4) diff --git a/m4/lls/write_cmd.suite.m4 b/m4/lls/write_cmd.suite.m4 new file mode 100644 index 00000000..9d8ee75a --- /dev/null +++ b/m4/lls/write_cmd.suite.m4 @@ -0,0 +1,87 @@ +[suite write_cmd] +caption = writers +[subcommand alsa] + purpose = native ALSA output plugin + [option device] + short_opt = d + summary = set PCM device + typestr = device + arg_info = required_arg + arg_type = string + default_val = default + [help] + Check for the presence of a /proc/asound/ directory to see if ALSA + is present in your kernel. The file /proc/asound/devices contains + all devices ALSA knows about. + [/help] + [option buffer-time] + short_opt = B + summary = duration of the ALSA buffer + typestr = milliseconds + arg_info = required_arg + arg_type = uint32 + default_val = 170 + [help] + This is only a hint as ALSA might pick a slightly different time, + depending on the sound hardware. The chosen value is shown in debug + output as BUFFER_TIME. + + If synchronization between multiple clients is desired, the same + buffer time should be configured for all clients. + [/help] +[subcommand ao] + purpose = output plugin for libao + [option driver] + short_opt = d + summary = select a output driver by name + typestr = name + arg_info = required_arg + arg_type = string + [help] + If this is not given, the driver with the highest priority (see below) + will be used. + [/help] + [option ao-option] + short_opt = o + summary = pass a key-value pair to the libao driver + typestr = key:value + arg_info = required_arg + arg_type = string + flag multiple + [help] + For each time this option is given, the supplied key-value pair is + appended to the list of options for the driver. Invalid keys are + silently ignored. + [/help] +[subcommand oss] + purpose = output plugin for the Open Sound System + [option device] + short_opt = d + summary = set PCM device + typestr = path + arg_info = required_arg + arg_type = string + default_val = /dev/dsp +[subcommand osx] + purpose = output plugin for Mac OS coreaudio + [option numbuffers] + short_opt = n + summary = number of audio buffers to allocate + typestr = num + arg_info = required_arg + arg_type = uint32 + default_val = 20 + [help] + Increase if you get buffer underruns. + [/help] +[subcommand file] + purpose = output plugin that writes to a local file + [option filename] + short_opt = f + summary = specify output file name + typestr = path + arg_info = required_arg + arg_type = string + [help] + Defaults to a random filename in ~/.paraslash. + [/help] diff --git a/man_util.bash b/man_util.bash deleted file mode 100755 index cb7519c9..00000000 --- a/man_util.bash +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env bash - -# Receivers, filters, writers are called "modules" in this script -print_modhelp() -{ - local ggo="$1" - - $GENGETOPT --show-detailed-help \ - --set-version "" \ - --set-package "" \ - < "$ggo" | awk 'BEGIN { - have_purpose=0 - have_usage=0 - } { - if (!have_purpose) { - if ($0 ~ /^ *$/) - next - printf(" (%s):", $0) - have_purpose=1 - next - } - if (!have_usage) { - if ($0 ~ /^Usage: /) { - have_usage=1 - } - next - } - print $0 - }' -} - -make_help() -{ - local target="$1" module ggo - - ggo="$GGO_DIR/$1.ggo" - $GENGETOPT --show-detailed-help \ - --set-version "$VERSION" \ - --set-package "para_$1" \ - < "$ggo" - - if [[ "$target" == 'recv' || "$target" == 'audiod' ]]; then - for module in $RECEIVERS; do - ggo="$GGO_DIR/${module}_recv.ggo" - [[ ! -f "$ggo" ]] && continue - printf "\nOptions for the $module receiver" - print_modhelp "$ggo" - done - fi - if [[ "$target" == 'filter' || "$target" == 'audiod' ]]; then - for module in $FILTERS; do - ggo="$GGO_DIR/${module}_filter.ggo" - [[ ! -f "$ggo" ]] && continue - printf "\nOptions for the $module filter" - print_modhelp "$ggo" - done - fi - if [[ "$target" == 'write' || "$target" == 'audiod' ]]; then - for module in $WRITERS; do - ggo="$GGO_DIR/${module}_write.ggo" - [[ ! -f "$ggo" ]] && continue - printf "\nOptions for the $module writer" - print_modhelp "$ggo" - done - fi -} - -set -u - -(($# != 1)) && exit 1 - -# These must be set by the caller (make or help2man) -export COMMAND_LISTS FILTERS GENGETOPT GGO_DIR HELP2MAN HELP2MAN_DIR \ - RECEIVERS VERSION WRITERS - -# If either --version or --help-xxx was given, we are being called by help2man -if [[ "$1" == "--version" ]]; then - echo "$VERSION" - exit $? -fi -if [[ "$1" =~ --help- ]]; then - make_help "${1#--help-}" - exit $? -fi - -# Called by make, run help2man -output_file="$1" -target="${output_file##*/para_}" -target="${target%.*}" # server, audiod, filter, ... -link="$HELP2MAN_DIR/para_$target" - -cl_opts= -for cl in $COMMAND_LISTS; do - cl_opts+=" --include $cl" -done - -# Create a symlink para_$target, pointing to this script. This hack is -# necessary because help2man always includes the name of the executable in its -# output. -ln -sf "$PWD/$0" "$link" - -# This will call us again twice, with either --help-$target or --version given. -$HELP2MAN --no-info --help-option "--help-$target" $cl_opts \ - "$link" > "$output_file" -if (($? != 0)); then - rm -f "$output_file" - exit 1 -fi diff --git a/mix.h b/mix.h index 60c4392f..5d68b2bb 100644 --- a/mix.h +++ b/mix.h @@ -4,7 +4,7 @@ * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file mix.h Mixer API (used by para_fade). */ +/** \file mix.h Mixer API for para_mixer. */ /** * Opaque structure which corresponds to an instance of a mixer. @@ -16,10 +16,14 @@ struct mixer_handle; /** * Operations provided by each mixer plugin. + * + * Each mixer plugin must define a non-static instance of this structure, with + * all pointers initialized to non-NULL values. No other symbols need to be + * exported. */ struct mixer { - /** Called on startup, must fill in all other members. */ - void (*init)(struct mixer *self); + /** Used to identify the mixer. */ + const char * const name; /** Return a handle that can be passed to other methods. */ int (*open)(const char *dev, struct mixer_handle **handle); /** Returns a string of all valid mixer channels. */ @@ -34,3 +38,6 @@ struct mixer { /** Free all resources associated with the given handle. */ void (*close)(struct mixer_handle **handle); }; + +/** Declared even if unsupported because it does not hurt and avoids ifdefs. */ +extern const struct mixer alsa_mixer, oss_mixer; diff --git a/mixer.c b/mixer.c new file mode 100644 index 00000000..0c08e2f6 --- /dev/null +++ b/mixer.c @@ -0,0 +1,593 @@ +/* + * Copyright (C) 1998 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file mixer.c A volume fader and alarm clock for OSS. */ + +#include +#include +#include + +#include "mixer.lsg.h" +#include "para.h" +#include "fd.h" +#include "string.h" +#include "mix.h" +#include "error.h" +#include "version.h" + +/** Array of error strings. */ +DEFINE_PARA_ERRLIST; + +/* At least one of the two is defined if this file gets compiled. */ +static const struct mixer *mixers[] = { +#ifdef HAVE_ALSA + &alsa_mixer, +#endif +#ifdef HAVE_OSS + &oss_mixer, +#endif +}; + +#define NUM_SUPPORTED_MIXERS (ARRAY_SIZE(mixers)) +#define FOR_EACH_MIXER(i) for ((i) = 0; (i) < NUM_SUPPORTED_MIXERS; (i)++) + +static struct lls_parse_result *lpr, *sub_lpr; + +#define CMD_PTR(_cmd) (lls_cmd(LSG_MIXER_CMD_ ## _cmd, mixer_suite)) +#define OPT_RESULT(_cmd, _opt) (lls_opt_result( \ + LSG_MIXER_ ## _cmd ## _OPT_ ## _opt, (LSG_MIXER_CMD_ ## _cmd == 0)? lpr : sub_lpr)) +#define OPT_GIVEN(_cmd, _opt) (lls_opt_given(OPT_RESULT(_cmd, _opt))) +#define OPT_STRING_VAL(_cmd, _opt) (lls_string_val(0, OPT_RESULT(_cmd, _opt))) +#define OPT_UINT32_VAL(_cmd, _opt) (lls_uint32_val(0, OPT_RESULT(_cmd, _opt))) + +typedef int (*mixer_subcommand_handler_t)(const struct mixer *, struct mixer_handle *); + +#define EXPORT_CMD(_cmd) const mixer_subcommand_handler_t \ + lsg_mixer_com_ ## _cmd ## _user_data = &com_ ## _cmd; + +static int loglevel; +static __printf_2_3 void date_log(int ll, const char *fmt, ...) +{ + va_list argp; + time_t t1; + struct tm *tm; + + if (ll < loglevel) + return; + time(&t1); + tm = localtime(&t1); + fprintf(stderr, "%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec); + va_start(argp, fmt); + vprintf(fmt, argp); + va_end(argp); +} +__printf_2_3 void (*para_log)(int, const char*, ...) = date_log; + +static int set_channel(const struct mixer *m, struct mixer_handle *h, + const char *channel) +{ + + PARA_NOTICE_LOG("using %s mixer channel\n", channel? + channel : "default"); + return m->set_channel(h, channel); +} + +static void millisleep(int ms) +{ + struct timespec ts; + + PARA_INFO_LOG("sleeping %dms\n", ms); + if (ms < 0) + return; + ts.tv_sec = ms / 1000, + ts.tv_nsec = (ms % 1000) * 1000 * 1000; + nanosleep(&ts, NULL); +} + +/* + * This implements the inverse function of t -> t^alpha, scaled to the time + * interval [0,T] and the range given by old_vol and new_vol. It returns the + * amount of milliseconds until the given volume is reached. + */ +static unsigned volume_time(double vol, double old_vol, double new_vol, + double T, double alpha) +{ + double c, d, x; + + if (old_vol < new_vol) { + c = old_vol; + d = new_vol; + } else { + c = new_vol; + d = old_vol; + } + + x = T * exp(log(((vol - c) / (d - c))) / alpha); + assert(x <= T); + if (old_vol < new_vol) + return x; + else + return T - x; +} + +/* Fade to new volume in fade_time seconds. */ +static int fade(const struct mixer *m, struct mixer_handle *h, uint32_t new_vol, + uint32_t fade_time) +{ + int i, T, old_vol, ret, slept, incr; + double ms, alpha; + uint32_t fe = OPT_UINT32_VAL(PARA_MIXER, FADE_EXPONENT); + + if (fade_time <= 0 || fe >= 100) { + ret = m->set(h, new_vol); + if (ret < 0) + return ret; + goto sleep; + } + alpha = (100 - fe) / 100.0; + ret = m->get(h); + if (ret < 0) + return ret; + old_vol = ret; + if (old_vol == new_vol) + goto sleep; + PARA_NOTICE_LOG("fading %s from %d to %u in %u seconds\n", + OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL), old_vol, + new_vol, fade_time); + incr = old_vol < new_vol? 1 : -1; + T = fade_time * 1000; + i = old_vol; + slept = 0; + do { + ms = volume_time(i + incr, old_vol, new_vol, T, alpha); + millisleep(ms - slept); + slept = ms; + i += incr; + ret = m->set(h, i); + if (ret < 0) + return ret; + } while (i != new_vol); + return 1; +sleep: + sleep(fade_time); + return ret; +} + +static int com_fade(const struct mixer *m, struct mixer_handle *h) +{ + uint32_t new_vol = OPT_UINT32_VAL(FADE, FADE_VOL); + uint32_t fade_time = OPT_UINT32_VAL(FADE, FADE_TIME); + return fade(m, h, new_vol, fade_time); +} +EXPORT_CMD(fade); + +static void client_cmd(const char *cmd) +{ + int ret, status, fds[3] = {0, 0, 0}; + pid_t pid; + char *cmdline = make_message(BINDIR "/para_client %s", cmd); + + PARA_NOTICE_LOG("%s\n", cmdline); + ret = para_exec_cmdline_pid(&pid, cmdline, fds); + free(cmdline); + if (ret < 0) { + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + goto fail; + } + do + pid = waitpid(pid, &status, 0); + while (pid == -1 && errno == EINTR); + if (pid < 0) { + PARA_ERROR_LOG("%s\n", strerror(errno)); + goto fail; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + goto fail; + return; +fail: + PARA_EMERG_LOG("command \"%s\" failed\n", cmd); + exit(EXIT_FAILURE); +} + +static void change_afs_mode(const char *afs_mode) +{ + char *cmd; + + client_cmd("stop"); + if (!afs_mode) + return; + cmd = make_message("select %s", afs_mode); + client_cmd(cmd); + free(cmd); +} + +static int set_initial_volume(const struct mixer *m, struct mixer_handle *h) +{ + int i, ret; + + for (i = 0; i < OPT_GIVEN(SLEEP, IVOL); i++) { + const char *val = lls_string_val(i, OPT_RESULT(SLEEP, IVOL)); + char *p, *ch, *arg = para_strdup(val); + int32_t iv; + p = strchr(arg, ':'); + if (p) { + *p = '\0'; + p++; + ch = arg; + } else { + p = arg; + ch = NULL; + } + ret = para_atoi32(p, &iv); + if (ret < 0) { + free(arg); + return ret; + } + ret = set_channel(m, h, ch); + if (!ch) + ch = "default"; + if (ret < 0) { + PARA_WARNING_LOG("ignoring channel %s\n", ch); + ret = 0; + } else { + PARA_INFO_LOG("initial volume %s: %d\n", ch, iv); + ret = m->set(h, iv); + } + free(arg); + if (ret < 0) + return ret; + } + return 1; +} + +static int com_sleep(const struct mixer *m, struct mixer_handle *h) +{ + time_t t1, wake_time_epoch; + unsigned int delay; + struct tm *tm; + int ret; + const char *wake_time = OPT_STRING_VAL(SLEEP, WAKE_TIME); + const char *fo_mood = OPT_STRING_VAL(SLEEP, FO_MOOD); + const char *fi_mood = OPT_STRING_VAL(SLEEP, FI_MOOD); + const char *sleep_mood = OPT_STRING_VAL(SLEEP, SLEEP_MOOD); + int fit = OPT_UINT32_VAL(SLEEP, FI_TIME); + int fot = OPT_UINT32_VAL(SLEEP, FO_TIME); + int fiv = OPT_UINT32_VAL(SLEEP, FI_VOL); + int fov = OPT_UINT32_VAL(SLEEP, FO_VOL); + int32_t hour, min = 0; + char *tmp; + char *wt = para_strdup(wake_time + (wake_time[0] == '+')); + + /* calculate wake time */ + time(&t1); + tmp = strchr(wt, ':'); + if (tmp) { + *tmp = '\0'; + tmp++; + ret = para_atoi32(tmp, &min); + if (ret < 0) { + free(wt); + return ret; + } + } + ret = para_atoi32(wt, &hour); + free(wt); + if (ret < 0) + return ret; + if (wake_time[0] == '+') { /* relative */ + t1 += hour * 60 * 60 + min * 60; + tm = localtime(&t1); + } else { + tm = localtime(&t1); + if (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min> min)) { + t1 += 86400; /* wake time is tomorrow */ + tm = localtime(&t1); + } + tm->tm_hour = hour; + tm->tm_min = min; + tm->tm_sec = 0; + } + wake_time_epoch = mktime(tm); + PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min); + client_cmd("stop"); + sleep(1); + if (fot) { + ret = set_initial_volume(m, h); + if (ret < 0) + return ret; + change_afs_mode(fo_mood); + client_cmd("play"); + ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL)); + if (ret < 0) + return ret; + ret = fade(m, h, fov, fot); + if (ret < 0) + return ret; + } else { + ret = m->set(h, fov); + if (ret < 0) + return ret; + } + if (OPT_GIVEN(SLEEP, SLEEP_MOOD)) { + change_afs_mode(sleep_mood); + client_cmd("play"); + } else + client_cmd("stop"); + if (!fit) + return 1; + change_afs_mode(fi_mood); + for (;;) { + time(&t1); + if (wake_time_epoch <= t1 + fit) + break; + delay = wake_time_epoch - t1 - fit; + PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n", + delay, delay / 3600, + (delay % 3600) / 60); + sleep(delay); + } + client_cmd("play"); + ret = fade(m, h, fiv, fit); + PARA_INFO_LOG("fade complete, returning\n"); + return ret; +} +EXPORT_CMD(sleep); + +static int com_snooze(const struct mixer *m, struct mixer_handle *h) +{ + int ret, val; + + if (OPT_UINT32_VAL(SNOOZE, SO_TIME) == 0) + return 1; + ret = m->get(h); + if (ret < 0) + return ret; + val = ret; + if (val < OPT_UINT32_VAL(SNOOZE, SO_VOL)) + ret = m->set(h, OPT_UINT32_VAL(SNOOZE, SO_VOL)); + else + ret = fade(m, h, OPT_UINT32_VAL(SNOOZE, SO_VOL), + OPT_UINT32_VAL(SNOOZE, SO_TIME)); + if (ret < 0) + return ret; + client_cmd("pause"); + PARA_NOTICE_LOG("%" PRIu32 " seconds snooze time...\n", + OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME)); + sleep(OPT_UINT32_VAL(SNOOZE, SNOOZE_TIME)); + client_cmd("play"); + return fade(m, h, OPT_UINT32_VAL(SNOOZE, SI_VOL), + OPT_UINT32_VAL(SNOOZE, SI_TIME)); +} +EXPORT_CMD(snooze); + +static int com_set(const struct mixer *m, struct mixer_handle *h) +{ + return m->set(h, OPT_UINT32_VAL(SET, VAL)); +} +EXPORT_CMD(set); + +static const struct mixer *get_mixer_or_die(void) +{ + int i; + + if (!OPT_GIVEN(PARA_MIXER, MIXER_API)) + i = 0; /* default: use first mixer */ + else + FOR_EACH_MIXER(i) + if (!strcmp(mixers[i]->name, + OPT_STRING_VAL(PARA_MIXER, MIXER_API))) + break; + if (i < NUM_SUPPORTED_MIXERS) { + PARA_NOTICE_LOG("using %s mixer API\n", mixers[i]->name); + return mixers[i]; + } + printf("available mixer APIs: "); + FOR_EACH_MIXER(i) + printf("%s%s%s ", i == 0? "[" : "", mixers[i]->name, + i == 0? "]" : ""); + printf("\n"); + exit(EXIT_FAILURE); +} + +static void show_subcommands(void) +{ + const struct lls_command *cmd; + int i; + printf("Subcommands:\n"); + for (i = 1; (cmd = lls_cmd(i, mixer_suite)); i++) { + const char *name = lls_command_name(cmd); + const char *purpose = lls_purpose(cmd); + printf("%-20s%s\n", name, purpose); + } +} + + +static int com_help(__a_unused const struct mixer *m, + __a_unused struct mixer_handle *h) +{ + const struct lls_command *cmd; + const struct lls_opt_result *r_l = OPT_RESULT(HELP, LONG); + char *txt, *errctx; + const char *name; + int ret; + + ret = lls_check_arg_count(sub_lpr, 0, 1, NULL); + if (ret < 0) + return ret; + if (lls_num_inputs(sub_lpr) == 0) { + show_subcommands(); + return 0; + } + name = lls_input(0, sub_lpr); + ret = lls(lls_lookup_subcmd(name, mixer_suite, &errctx)); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + return ret; + } + cmd = lls_cmd(ret, mixer_suite); + if (lls_opt_given(r_l)) + txt = lls_long_help(cmd); + else + txt = lls_short_help(cmd); + printf("%s", txt); + free(txt); + return 0; +} +EXPORT_CMD(help); + +static void handle_help_flags(void) +{ + char *help; + + if (OPT_GIVEN(PARA_MIXER, DETAILED_HELP)) + help = lls_long_help(CMD_PTR(PARA_MIXER)); + else if (OPT_GIVEN(PARA_MIXER, HELP)) + help = lls_short_help(CMD_PTR(PARA_MIXER)); + else + return; + printf("%s", help); + free(help); + show_subcommands(); + exit(EXIT_SUCCESS); +} + +static int parse_and_merge_config_file(const struct lls_command *cmd) +{ + int ret; + int cf_argc; + char **cf_argv; + char *cf, *errctx = NULL; + struct lls_parse_result **lprp, *cf_lpr, *merged_lpr; + void *map; + size_t sz; + const char *subcmd_name; + + if (cmd == lls_cmd(0, mixer_suite)) { + lprp = &lpr; + subcmd_name = NULL; + } else { + lprp = &sub_lpr; + subcmd_name = lls_command_name(cmd); + } + if (OPT_GIVEN(PARA_MIXER, CONFIG_FILE)) + cf = para_strdup(OPT_STRING_VAL(PARA_MIXER, CONFIG_FILE)); + else { + char *home = para_homedir(); + cf = make_message("%s/.paraslash/mixer.conf", home); + free(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 free_cf; + if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && + OPT_GIVEN(PARA_MIXER, CONFIG_FILE)) + goto free_cf; + } else { + ret = lls(lls_convert_config(map, sz, subcmd_name, &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, &cf_lpr, &errctx)); + lls_free_argv(cf_argv); + if (ret < 0) + goto free_cf; + ret = lls(lls_merge(*lprp, cf_lpr, cmd, &merged_lpr, &errctx)); + lls_free_parse_result(cf_lpr, cmd); + if (ret < 0) + goto free_cf; + lls_free_parse_result(*lprp, cmd); + *lprp = merged_lpr; + loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL); + } + ret = 1; +free_cf: + free(cf); + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + return ret; +} + +/** + * The main function of para_mixer. + * + * The executable is linked with the alsa or the oss mixer API, or both. It has + * a custom log function which prefixes log messages with the current date. + * + * \param argc Argument counter. + * \param argv Argument vector. + * + * \return EXIT_SUCCESS or EXIT_FAILURE. + */ +int main(int argc, char *argv[]) +{ + const struct lls_command *cmd = CMD_PTR(PARA_MIXER); + int ret; + char *errctx; + const char *subcmd; + const struct mixer *m; + struct mixer_handle *h; + unsigned n; + + ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx)); + if (ret < 0) + goto fail; + loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL); + version_handle_flag("mixer", OPT_GIVEN(PARA_MIXER, VERSION)); + handle_help_flags(); + + n = lls_num_inputs(lpr); + if (n == 0) { + show_subcommands(); + ret = 0; + goto free_lpr; + } + ret = parse_and_merge_config_file(cmd); + if (ret < 0) + goto free_lpr; + subcmd = lls_input(0, lpr); + ret = lls(lls_lookup_subcmd(subcmd, mixer_suite, &errctx)); + if (ret < 0) + goto fail; + cmd = lls_cmd(ret, mixer_suite); + ret = lls(lls_parse(n, argv + argc - n, cmd, &sub_lpr, &errctx)); + if (ret < 0) + goto free_lpr; + ret = parse_and_merge_config_file(cmd); + if (ret < 0) + goto free_lpr; + m = get_mixer_or_die(); + ret = m->open(OPT_STRING_VAL(PARA_MIXER, MIXER_DEVICE), &h); + if (ret < 0) + goto free_sub_lpr; + ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL)); + if (ret == -E_BAD_CHANNEL) { + char *channels = m->get_channels(h); + printf("Available channels: %s\n", channels); + free(channels); + } + if (ret < 0) + goto close_mixer; + ret = (*(mixer_subcommand_handler_t *)(lls_user_data(cmd)))(m ,h); +close_mixer: + m->close(&h); +free_sub_lpr: + lls_free_parse_result(sub_lpr, cmd); +free_lpr: + lls_free_parse_result(lpr, CMD_PTR(PARA_MIXER)); + if (ret >= 0) + return EXIT_SUCCESS; +fail: + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + return EXIT_FAILURE; +} diff --git a/mm.c b/mm.c index 92856ec3..47e88864 100644 --- a/mm.c +++ b/mm.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "para.h" #include "error.h" diff --git a/mood.c b/mood.c index 79f47e5a..acc5583f 100644 --- a/mood.c +++ b/mood.c @@ -8,6 +8,7 @@ #include #include +#include #include "para.h" #include "error.h" diff --git a/mp3_afh.c b/mp3_afh.c index 2115f71c..e5d0ff13 100644 --- a/mp3_afh.c +++ b/mp3_afh.c @@ -657,6 +657,7 @@ static int mp3_read_info(unsigned char *map, size_t numbytes, int fd, tv_divide(afhi->chunks_total, &total_time, &afhi->chunk_tv); PARA_DEBUG_LOG("%" PRIu32 "chunks, each %lums\n", afhi->chunks_total, tv2ms(&afhi->chunk_tv)); + set_max_chunk_size(afhi); ret = mp3_get_id3(map, numbytes, fd, &afhi->tags); afhi->techinfo = make_message("%cbr, %s, %s tags", vbr? 'v' : 'c', header_mode(&header), tag_versions[ret]); diff --git a/mp3dec_filter.c b/mp3dec_filter.c index 5f6935d1..b053aaa8 100644 --- a/mp3dec_filter.c +++ b/mp3dec_filter.c @@ -8,12 +8,12 @@ #include #include +#include +#include "filter_cmd.lsg.h" #include "para.h" -#include "mp3dec_filter.cmdline.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "error.h" @@ -171,25 +171,15 @@ err: static void mp3dec_open(struct filter_node *fn) { struct private_mp3dec_data *pmd = para_calloc(sizeof(*pmd)); - struct mp3dec_filter_args_info *mp3_conf = fn->conf; fn->private_data = pmd; mad_stream_init(&pmd->stream); mad_frame_init(&pmd->frame); mad_synth_init(&pmd->synth); - if (mp3_conf->ignore_crc_given) + if (FILTER_CMD_OPT_GIVEN(MP3DEC, IGNORE_CRC, fn->lpr)) mad_stream_options(&pmd->stream, MAD_OPTION_IGNORECRC); } -static int mp3dec_parse_config(int argc, char **argv, void **config) -{ - struct mp3dec_filter_args_info *conf = para_calloc(sizeof(*conf)); - - mp3dec_filter_cmdline_parser(argc, argv, conf); - *config = conf; - return 1; -} - static int mp3dec_execute(struct btr_node *btrn, const char *cmd, char **result) { struct filter_node *fn = btr_context(btrn); @@ -198,28 +188,10 @@ static int mp3dec_execute(struct btr_node *btrn, const char *cmd, char **result) return decoder_execute(cmd, pmd->sample_rate, pmd->channels, result); } -static void mp3dec_free_config(void *conf) -{ - mp3dec_filter_cmdline_parser_free(conf); -} -/** - * The init function of the mp3dec filter. - * - * \param f Pointer to the filter struct to initialize. - * - * \sa filter::init. - */ -void mp3dec_filter_init(struct filter *f) -{ - struct mp3dec_filter_args_info dummy; - - mp3dec_filter_cmdline_parser_init(&dummy); - f->open = mp3dec_open; - f->close = mp3dec_close; - f->parse_config = mp3dec_parse_config; - f->free_config = mp3dec_free_config; - f->pre_select = generic_filter_pre_select; - f->post_select = mp3dec_post_select; - f->execute = mp3dec_execute; - f->help = (struct ggo_help)DEFINE_GGO_HELP(mp3dec_filter); -} +const struct filter lsg_filter_cmd_com_mp3dec_user_data = { + .open = mp3dec_open, + .close = mp3dec_close, + .pre_select = generic_filter_pre_select, + .post_select = mp3dec_post_select, + .execute = mp3dec_execute, +}; diff --git a/net.c b/net.c index eaa1cfb8..50243673 100644 --- a/net.c +++ b/net.c @@ -603,7 +603,7 @@ static inline int estimated_header_overhead(const int af_type) */ int generic_max_transport_msg_size(int sockfd) { - struct sockaddr_storage ss = {0}; + struct sockaddr_storage ss = {.ss_family = 0}; socklen_t sslen = sizeof(ss); int af_type = AF_INET; @@ -629,7 +629,7 @@ int generic_max_transport_msg_size(int sockfd) */ char *remote_name(int fd) { - struct sockaddr_storage ss = {0}; + struct sockaddr_storage ss = {.ss_family = 0}; const struct sockaddr *sa; socklen_t sslen = sizeof(ss); char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; @@ -818,25 +818,37 @@ int dccp_available_ccids(uint8_t **ccid_array) return nccids; } -/** - * Prepare a structure for \p AF_UNIX socket addresses. - * - * \param u Pointer to the struct to be prepared. - * \param name The socket pathname. +/* + * Prepare a structure for AF_UNIX socket addresses. * - * This just copies \a name to the sun_path component of \a u. + * This just copies name to the sun_path component of u, prepending a zero byte + * if abstract sockets are supported. * - * \return Positive on success, \p -E_NAME_TOO_LONG if \a name is longer - * than \p UNIX_PATH_MAX. + * The first call to this function tries to bind a socket to the abstract name + * space. The result of this test is stored in a static variable. Subsequent + * calls read this variable and create abstract sockets on systems that support + * them. */ -static int init_unix_addr(struct sockaddr_un *u, const char *name, - bool abstract) +static int init_unix_addr(struct sockaddr_un *u, const char *name) { - if (strlen(name) + abstract >= UNIX_PATH_MAX) + static int use_abstract; + + if (strlen(name) + 1 >= UNIX_PATH_MAX) return -E_NAME_TOO_LONG; memset(u->sun_path, 0, UNIX_PATH_MAX); u->sun_family = PF_UNIX; - strcpy(u->sun_path + abstract, name); + if (use_abstract == 0) { /* executed only once */ + int fd = socket(PF_UNIX, SOCK_STREAM, 0); + memcpy(u->sun_path, "\0x\0", 3); + if (bind(fd, (struct sockaddr *)u, sizeof(*u)) == 0) + use_abstract = 1; /* yes */ + else + use_abstract = -1; /* no */ + close(fd); + PARA_NOTICE_LOG("%susing abstract socket namespace\n", + use_abstract == 1? "" : "not "); + } + strcpy(u->sun_path + (use_abstract == 1? 1 : 0), name); return 1; } @@ -844,28 +856,22 @@ static int init_unix_addr(struct sockaddr_un *u, const char *name, * Create a socket for local communication and listen on it. * * \param name The socket pathname. - * \param mode The desired permissions of the socket. * * This function creates a passive local socket for sequenced, reliable, * two-way, connection-based byte streams. The socket file descriptor is set to * nonblocking mode and listen(2) is called to prepare the socket for * accepting incoming connection requests. * - * If mode is zero, an abstract socket (a non-portable Linux extension) is - * created. In this case the socket name has no connection with filesystem - * pathnames. - * * \return The file descriptor on success, negative error code on failure. * * \sa socket(2), \sa bind(2), \sa chmod(2), listen(2), unix(7). */ -int create_local_socket(const char *name, mode_t mode) +int create_local_socket(const char *name) { struct sockaddr_un unix_addr; int fd, ret; - bool abstract = mode == 0; - ret = init_unix_addr(&unix_addr, name, abstract); + ret = init_unix_addr(&unix_addr, name); if (ret < 0) return ret; ret = socket(PF_UNIX, SOCK_STREAM, 0); @@ -880,7 +886,9 @@ int create_local_socket(const char *name, mode_t mode) ret = -ERRNO_TO_PARA_ERROR(errno); goto err; } - if (!abstract) { + if (unix_addr.sun_path[0] != 0) { /* pathname socket */ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP + | S_IROTH | S_IWOTH; ret = -E_CHMOD; if (chmod(name, mode) < 0) goto err; @@ -917,14 +925,7 @@ int connect_local_socket(const char *name) fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -ERRNO_TO_PARA_ERROR(errno); - /* first try (linux-only) abstract socket */ - ret = init_unix_addr(&unix_addr, name, true); - if (ret < 0) - goto err; - if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) != -1) - return fd; - /* next try pathname socket */ - ret = init_unix_addr(&unix_addr, name, false); + ret = init_unix_addr(&unix_addr, name); if (ret < 0) goto err; if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) != -1) diff --git a/net.h b/net.h index b2bb47c9..a70954a9 100644 --- a/net.h +++ b/net.h @@ -147,7 +147,7 @@ int recv_bin_buffer(int fd, char *buf, size_t size); int recv_buffer(int fd, char *buf, size_t size); int para_accept(int fd, fd_set *rfds, void *addr, socklen_t size, int *new_fd); -int create_local_socket(const char *name, mode_t mode); +int create_local_socket(const char *name); int connect_local_socket(const char *name); int recv_cred_buffer(int, char *, size_t); ssize_t send_cred_buffer(int, char*); diff --git a/ogg_afh_common.c b/ogg_afh_common.c index adab7f48..734fd586 100644 --- a/ogg_afh_common.c +++ b/ogg_afh_common.c @@ -127,19 +127,18 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi, { ogg_sync_state oss; ogg_page op; - long len = numbytes; char *buf; int ret, i, j, frames_per_chunk, ct_size; long long unsigned num_frames = 0; ogg_sync_init(&oss); ret = -E_OGG_SYNC; - buf = ogg_sync_buffer(&oss, len); + buf = ogg_sync_buffer(&oss, numbytes); if (!buf) goto out; - memcpy(buf, map, len); + memcpy(buf, map, numbytes); ret = -E_OGG_SYNC; - if (ogg_sync_wrote(&oss, len) < 0) + if (ogg_sync_wrote(&oss, numbytes) < 0) goto out; ret = process_ogg_packets(&oss, afhi, ci); if (ret < 0) @@ -182,6 +181,7 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi, } } afhi->chunks_total = j; + set_max_chunk_size(afhi); set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv); ret = 0; out: diff --git a/oggdec_filter.c b/oggdec_filter.c index 04be7020..9fa80c55 100644 --- a/oggdec_filter.c +++ b/oggdec_filter.c @@ -12,7 +12,6 @@ #include "para.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "error.h" @@ -264,16 +263,10 @@ out: return ret; } -/** - * The init function of the ogg vorbis decoder. - * - * \param f Its fields are filled in by the function. - */ -void oggdec_filter_init(struct filter *f) -{ - f->open = ogg_open; - f->close = ogg_close; - f->pre_select = ogg_pre_select; - f->post_select = ogg_post_select; - f->execute = oggdec_execute; -} +const struct filter lsg_filter_cmd_com_oggdec_user_data = { + .open = ogg_open, + .close = ogg_close, + .pre_select = ogg_pre_select, + .post_select = ogg_post_select, + .execute = oggdec_execute +}; diff --git a/opusdec_filter.c b/opusdec_filter.c index 28222985..6f9d526c 100644 --- a/opusdec_filter.c +++ b/opusdec_filter.c @@ -50,7 +50,6 @@ #include "para.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "error.h" @@ -284,18 +283,10 @@ static void opusdec_pre_select(struct sched *s, void *context) return sched_min_delay(s); } -/** - * The init function of the opusdec filter. - * - * \param f Pointer to the filter struct to initialize. - * - * \sa filter::init. - */ -void opusdec_filter_init(struct filter *f) -{ - f->open = opusdec_open; - f->close = opusdec_close; - f->pre_select = opusdec_pre_select; - f->post_select = opusdec_post_select; - f->execute = opusdec_execute; -} +const struct filter lsg_filter_cmd_com_opusdec_user_data = { + .open = opusdec_open, + .close = opusdec_close, + .pre_select = opusdec_pre_select, + .post_select = opusdec_post_select, + .execute = opusdec_execute, +}; diff --git a/oss_mix.c b/oss_mix.c index 8e87452b..7fbdba5b 100644 --- a/oss_mix.c +++ b/oss_mix.c @@ -135,19 +135,13 @@ static void oss_mix_close(struct mixer_handle **handle) *handle = NULL; } -/** - * The init function of the OSS mixer. - * - * \param self The structure to initialize. - * - * \sa struct \ref mixer, \ref alsa_mix_init(). - */ -void oss_mix_init(struct mixer *self) -{ - self->open = oss_mix_open; - self->get_channels = oss_mix_get_channels; - self->set_channel = oss_mix_set_channel; - self->get = oss_mix_get; - self->set = oss_mix_set; - self->close = oss_mix_close; -} +/** The mixer operations for the OSS mixer. */ +const struct mixer oss_mixer = { + .name = "oss", + .open = oss_mix_open, + .get_channels = oss_mix_get_channels, + .set_channel = oss_mix_set_channel, + .close = oss_mix_close, + .get = oss_mix_get, + .set = oss_mix_set +}; diff --git a/oss_write.c b/oss_write.c index 20186667..6f646c90 100644 --- a/oss_write.c +++ b/oss_write.c @@ -9,17 +9,16 @@ #include #include #include +#include +#include "write_cmd.lsg.h" #include "para.h" #include "fd.h" #include "string.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "write.h" -#include "write_common.h" -#include "oss_write.cmdline.h" #include "error.h" /** Data specific to the oss writer. */ @@ -106,11 +105,11 @@ static int oss_init(struct writer_node *wn, unsigned sample_rate, { int ret, format; unsigned ch, rate; - struct oss_write_args_info *conf = wn->conf; struct private_oss_write_data *powd = para_calloc(sizeof(*powd)); + const char *dev = WRITE_CMD_OPT_STRING_VAL(OSS, DEVICE, wn->lpr); - PARA_INFO_LOG("opening %s\n", conf->device_arg); - ret = para_open(conf->device_arg, O_WRONLY, 0); + PARA_INFO_LOG("opening %s\n", dev); + ret = para_open(dev, O_WRONLY, 0); if (ret < 0) goto err_free; powd->fd = ret; @@ -175,8 +174,7 @@ err: close(powd->fd); err_free: free(powd); - PARA_ERROR_LOG("failed to init %s: %s\n", conf->device_arg, - para_strerror(-ret)); + PARA_ERROR_LOG("failed to init %s: %s\n", dev, para_strerror(-ret)); return ret; } @@ -240,37 +238,8 @@ out: return ret; } -__malloc static void *oss_parse_config_or_die(int argc, char **argv) -{ - struct oss_write_args_info *conf = para_calloc(sizeof(*conf)); - - /* exits on errors */ - oss_write_cmdline_parser(argc, argv, conf); - return conf; -} - -static void oss_free_config(void *conf) -{ - oss_write_cmdline_parser_free(conf); -} - -/** - * The init function of the oss writer. - * - * \param w Pointer to the writer to initialize. - * - * \sa struct writer. - */ -void oss_write_init(struct writer *w) -{ - struct oss_write_args_info dummy; - - oss_write_cmdline_parser_init(&dummy); - w->close = oss_close; - w->pre_select = oss_pre_select; - w->post_select = oss_post_select; - w->parse_config_or_die = oss_parse_config_or_die; - w->free_config = oss_free_config; - w->help = (struct ggo_help)DEFINE_GGO_HELP(oss_write); - oss_write_cmdline_parser_free(&dummy); -} +const struct writer lsg_write_cmd_com_oss_user_data = { + .pre_select = oss_pre_select, + .post_select = oss_post_select, + .close = oss_close, +}; diff --git a/osx_write.c b/osx_write.c deleted file mode 100644 index 18a2c084..00000000 --- a/osx_write.c +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (C) 2006 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file osx_write.c paraslash's output plugin for MacOs */ - -/* - * based on mosx-mpg123, by Guillaume Outters and Steven A. Kortze - * - */ - -#include -#include - -#include "para.h" -#include "fd.h" -#include "string.h" -#include "list.h" -#include "sched.h" -#include "ggo.h" -#include "buffer_tree.h" -#include "write.h" -#include "write_common.h" -#include "osx_write.cmdline.h" -#include "ipc.h" -#include "error.h" - -#include -#include -#include - -/** Data specific to the osx writer. */ -struct private_osx_write_data { - /** The main CoreAudio handle. */ - AudioUnit audio_unit; - /** True if we wrote some audio data. */ - bool playing; - /** Sample rate of the current audio stream. */ - unsigned sample_rate; - /** Sample format of the current audio stream */ - unsigned sample_format; - /** Number of channels of the current audio stream. */ - unsigned channels; - /** - * Serializes access to buffer tree nodes between the writer and - * the callback which runs in a different thread. - */ - int mutex; - /** - * The btr node of the callback. - * - * Although access to the btr node is serialized between the writer and - * the callback via the above mutex, this does not stop other buffer - * tree nodes, for example the decoder, to race against the osx - * callback. - * - * However, since all operations on buffer tree nodes are local in the - * sense that they only affect one level in the buffer tree (i.e. - * parent or child nodes, but not the grandparent or the - * grandchildren), we may work around this problem by using another - * buffer tree node for the callback. - * - * The writer grabs the mutex in its post_select method and pushes down - * the buffers to the callback node. - */ - struct btr_node *callback_btrn; -}; - -/* This function writes the address and the number of bytes to one end of the socket. - * The post_select() function then fills the buffer and notifies the callback also - * through the socket. - */ -static OSStatus osx_callback(void *cb_arg, __a_unused AudioUnitRenderActionFlags *af, - __a_unused const AudioTimeStamp *ts, __a_unused UInt32 bus_number, - __a_unused UInt32 num_frames, AudioBufferList *abl) -{ - int i; - struct writer_node *wn = cb_arg; - struct private_osx_write_data *powd; - size_t samples_have, samples_want = 0; - - powd = wn->private_data; - mutex_lock(powd->mutex); - powd = wn->private_data; - if (!powd || !wn->btrn) - goto out; - /* - * We fill with zeros if no data was yet written and we do not have - * enough to fill all buffers. - */ - if (!powd->playing) { - size_t want = 0, have = - btr_get_input_queue_size(powd->callback_btrn); - for (i = 0; i < abl->mNumberBuffers; i++) - want += abl->mBuffers[i].mDataByteSize; - if (have < want) { - PARA_DEBUG_LOG("deferring playback (have = %zu < %zu = want)\n", - have, want); - for (i = 0; i < abl->mNumberBuffers; i++) - memset(abl->mBuffers[i].mData, 0, - abl->mBuffers[i].mDataByteSize); - goto out; - } - powd->playing = true; - } - - for (i = 0; i < abl->mNumberBuffers; i++) { - /* what we have to fill */ - void *dest = abl->mBuffers[i].mData; - size_t sz = abl->mBuffers[i].mDataByteSize, samples, bytes; - - samples_want = sz / wn->min_iqs; - while (samples_want > 0) { - char *buf; - btr_merge(powd->callback_btrn, wn->min_iqs); - samples_have = btr_next_buffer(powd->callback_btrn, &buf) / wn->min_iqs; - //PARA_INFO_LOG("i: %d want %zu samples to addr %p, have: %zu\n", i, samples_want, - // dest, samples_have); - samples = PARA_MIN(samples_have, samples_want); - if (samples == 0) - break; - bytes = samples * wn->min_iqs; - memcpy(dest, buf, bytes); - btr_consume(powd->callback_btrn, bytes); - samples_want -= samples; - dest += bytes; - } - if (samples_want == 0) - continue; - if (btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF) >= 0) - PARA_INFO_LOG("zero-padding (%zu samples)\n", - samples_want); - memset(dest, 0, samples_want * wn->min_iqs); - break; - } -out: - mutex_unlock(powd->mutex); - return noErr; -} - -static int core_audio_init(struct writer_node *wn) -{ - struct private_osx_write_data *powd = para_calloc(sizeof(*powd)); - Component comp; - int ret; - int32_t val; - AURenderCallbackStruct input_callback; - ComponentDescription desc = { - .componentType = kAudioUnitType_Output, - .componentSubType = kAudioUnitSubType_DefaultOutput, - .componentManufacturer = kAudioUnitManufacturer_Apple, - }; - AudioStreamBasicDescription format = { - .mFormatID = kAudioFormatLinearPCM, - .mFramesPerPacket = 1, - }; - struct btr_node *btrn = wn->btrn; - struct btr_node_description bnd; - - PARA_INFO_LOG("wn: %p\n", wn); - ret = -E_DEFAULT_COMP; - comp = FindNextComponent(NULL, &desc); - if (!comp) - goto e0; - ret = -E_OPEN_COMP; - if (OpenAComponent(comp, &powd->audio_unit)) - goto e0; - ret = -E_UNIT_INIT; - if (AudioUnitInitialize(powd->audio_unit)) - goto e1; - get_btr_sample_rate(btrn, &val); - powd->sample_rate = val; - get_btr_channels(btrn, &val); - powd->channels = val; - get_btr_sample_format(btrn, &val); - powd->sample_format = val; - /* - * Choose PCM format. We tell the Output Unit what format we're going - * to supply data to it. This is necessary if you're providing data - * through an input callback AND you want the DefaultOutputUnit to do - * any format conversions necessary from your format to the device's - * format. - */ - - format.mSampleRate = powd->sample_rate; - format.mChannelsPerFrame = powd->channels; - - switch (powd->sample_format) { - case SF_S8: - case SF_U8: - wn->min_iqs = powd->channels; - format.mBitsPerChannel = 8; - format.mBytesPerPacket = powd->channels; - format.mFormatFlags |= kLinearPCMFormatFlagIsPacked; - break; - default: - wn->min_iqs = powd->channels * 2; - format.mBytesPerPacket = powd->channels * 2; - format.mBitsPerChannel = 16; - format.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; - } - format.mBytesPerFrame = format.mBytesPerPacket; - - if (powd->sample_format == SF_S16_BE || powd->sample_format == SF_U16_BE) - format.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; - - input_callback = (AURenderCallbackStruct){osx_callback, wn}; - ret = -E_STREAM_FORMAT; - if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, 0, &format, sizeof(format))) - goto e2; - ret = -E_ADD_CALLBACK; - if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, &input_callback, - sizeof(input_callback)) < 0) - goto e2; - - ret = mutex_new(); - if (ret < 0) - goto e2; - powd->mutex = ret; - /* set up callback btr node */ - bnd.name = "cb_node"; - bnd.parent = btrn; - bnd.child = NULL; - bnd.handler = NULL; - bnd.context = powd; - powd->callback_btrn = btr_new_node(&bnd); - wn->private_data = powd; - return 1; -e2: - AudioUnitUninitialize(powd->audio_unit); -e1: - CloseComponent(powd->audio_unit); -e0: - free(powd); - wn->private_data = NULL; - return ret; -} - -__malloc static void *osx_write_parse_config_or_die(int argc, char **argv) -{ - struct osx_write_args_info *conf = para_calloc(sizeof(*conf)); - - /* exits on errors */ - osx_write_cmdline_parser(argc, argv, conf); - return conf; -} - -static void osx_free_config(void *conf) -{ - osx_write_cmdline_parser_free(conf); -} - -static void osx_write_close(struct writer_node *wn) -{ - struct private_osx_write_data *powd = wn->private_data; - - if (!powd) - return; - PARA_INFO_LOG("closing writer node %p\n", wn); - mutex_destroy(powd->mutex); - free(powd); - wn->private_data = NULL; -} - -/* must be called with the mutex held */ -static inline bool need_drain_delay(struct private_osx_write_data *powd) -{ - if (!powd->playing) - return false; - return btr_get_input_queue_size(powd->callback_btrn) != 0; -} - -static void osx_write_pre_select(struct sched *s, void *context) -{ - struct writer_node *wn = context; - struct private_osx_write_data *powd = wn->private_data; - int ret; - bool drain_delay_nec = false; - - if (!powd) { - ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF); - if (ret != 0) - sched_min_delay(s); - return; - } - - mutex_lock(powd->mutex); - ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_INTERNAL); - if (ret < 0) - drain_delay_nec = need_drain_delay(powd); - mutex_unlock(powd->mutex); - - if (drain_delay_nec) - return sched_request_timeout_ms(50, s); - if (ret != 0) - return sched_min_delay(s); - sched_request_timeout_ms(50, s); -} - -static int osx_write_post_select(__a_unused struct sched *s, void *context) -{ - struct writer_node *wn = context; - struct private_osx_write_data *powd = wn->private_data; - struct btr_node *btrn = wn->btrn; - int ret; - - ret = task_get_notification(wn->task); - if (ret < 0) - goto fail; - if (!powd) { - ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF); - if (ret == 0) - return 0; - if (ret < 0) - goto fail; - ret = core_audio_init(wn); - if (ret < 0) - goto fail; - powd = wn->private_data; - ret = -E_UNIT_START; - if (AudioOutputUnitStart(powd->audio_unit) != noErr) { - AudioUnitUninitialize(powd->audio_unit); - CloseComponent(powd->audio_unit); - btr_remove_node(&powd->callback_btrn); - goto fail; - } - } - mutex_lock(powd->mutex); - ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_INTERNAL); - if (ret > 0) - btr_pushdown(btrn); - if (ret < 0 && need_drain_delay(powd)) - ret = 0; - mutex_unlock(powd->mutex); - if (ret >= 0) - return 0; -fail: - assert(ret < 0); - if (powd && powd->callback_btrn) { - AudioOutputUnitStop(powd->audio_unit); - AudioUnitUninitialize(powd->audio_unit); - CloseComponent(powd->audio_unit); - btr_remove_node(&powd->callback_btrn); - } - btr_remove_node(&wn->btrn); - PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); - return ret; -} - -/** - * The init function of the osx writer. - * - * \param w Filled in by the function. - */ -void osx_write_init(struct writer *w) -{ - struct osx_write_args_info dummy; - - osx_write_cmdline_parser_init(&dummy); - w->close = osx_write_close; - w->pre_select = osx_write_pre_select; - w->post_select = osx_write_post_select; - w->parse_config_or_die = osx_write_parse_config_or_die; - w->free_config = osx_free_config; - w->help = (struct ggo_help)DEFINE_GGO_HELP(osx_write); - osx_write_cmdline_parser_free(&dummy); -} diff --git a/para.h b/para.h index 12d23639..3cd1b162 100644 --- a/para.h +++ b/para.h @@ -223,24 +223,9 @@ enum sample_format {SAMPLE_FORMATS}; #define SAMPLE_FORMAT(a, b) b /** \endcond sample_format */ -/** Debug loglevel, gets really noisy. */ -#define LL_DEBUG 0 -/** Still noisy, but won't fill your disk. */ -#define LL_INFO 1 -/** Normal, but significant event. */ -#define LL_NOTICE 2 -/** Unexpected event that can be handled. */ -#define LL_WARNING 3 -/** Unhandled error condition. */ -#define LL_ERROR 4 -/** System might be unreliable. */ -#define LL_CRIT 5 -/** Last message before exit. */ -#define LL_EMERG 6 -/** Number of all loglevels. */ -#define NUM_LOGLEVELS 7 - -/** \cond log */ +/** Debug, Info, etc. */ +enum loglevels {LOGLEVELS, NUM_LOGLEVELS}; + #define PARA_DEBUG_LOG(f,...) para_log(LL_DEBUG, "%s: " f, __FUNCTION__, ## __VA_ARGS__) #define PARA_INFO_LOG(f,...) para_log(LL_INFO, "%s: " f, __FUNCTION__, ## __VA_ARGS__) #define PARA_NOTICE_LOG(f,...) para_log(LL_NOTICE, "%s: " f, __FUNCTION__, ## __VA_ARGS__) @@ -248,4 +233,3 @@ enum sample_format {SAMPLE_FORMATS}; #define PARA_ERROR_LOG(f,...) para_log(LL_ERROR, "%s: " f, __FUNCTION__, ## __VA_ARGS__) #define PARA_CRIT_LOG(f,...) para_log(LL_CRIT, "%s: " f, __FUNCTION__, ## __VA_ARGS__) #define PARA_EMERG_LOG(f,...) para_log(LL_EMERG, "%s: " f, __FUNCTION__, ## __VA_ARGS__) -/** \endcond log */ diff --git a/play.c b/play.c index fac551aa..56838e82 100644 --- a/play.c +++ b/play.c @@ -7,14 +7,17 @@ /** \file play.c Paraslash's standalone player. */ #include -#include #include +#include +#include +#include "recv_cmd.lsg.h" +#include "play_cmd.lsg.h" +#include "write_cmd.lsg.h" +#include "play.lsg.h" #include "para.h" #include "list.h" -#include "play.cmdline.h" #include "error.h" -#include "ggo.h" #include "buffer_tree.h" #include "version.h" #include "string.h" @@ -23,7 +26,6 @@ #include "afh.h" #include "recv.h" #include "write.h" -#include "write_common.h" #include "fd.h" /** @@ -39,6 +41,15 @@ /** Array of error strings. */ DEFINE_PARA_ERRLIST; +static struct lls_parse_result *play_lpr; + +#define CMD_PTR (lls_cmd(0, play_suite)) +#define OPT_RESULT(_name) \ + (lls_opt_result(LSG_PLAY_PARA_PLAY_OPT_ ## _name, play_lpr)) +#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name))) +#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name))) +#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name))) + /** * Describes a request to change the state of para_play. * @@ -98,13 +109,14 @@ struct play_task { char *afhi_txt; }; -/* Activate the afh receiver. */ -extern void afh_recv_init(struct receiver *r); -#undef AFH_RECEIVER -/** Initialization code for a receiver struct. */ -#define AFH_RECEIVER {.name = "afh", .init = afh_recv_init}, -/** This expands to the array of all receivers. */ -DEFINE_RECEIVER_ARRAY; +typedef int (*play_cmd_handler_t)(struct lls_parse_result *lpr); +struct play_command_info { + play_cmd_handler_t handler; +}; +#define EXPORT_PLAY_CMD_HANDLER(_cmd) \ + const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \ + .handler = com_ ## _cmd \ + }; static int loglevel = LL_WARNING; @@ -113,90 +125,109 @@ INIT_STDERR_LOGGING(loglevel); char *stat_item_values[NUM_STAT_ITEMS] = {NULL}; -/** Iterate over all files in the playlist. */ -#define FOR_EACH_PLAYLIST_FILE(i) for (i = 0; i < conf.inputs_num; i++) -static struct play_args_info conf; - static struct sched sched = {.max_fileno = 0}; -static struct play_task play_task; -static struct receiver *afh_recv; +static struct play_task play_task, *pt = &play_task; -static void check_afh_receiver_or_die(void) -{ - int i; +#define AFH_RECV_CMD (lls_cmd(LSG_RECV_CMD_CMD_AFH, recv_cmd_suite)) +#define AFH_RECV ((struct receiver *)lls_user_data(AFH_RECV_CMD)) - FOR_EACH_RECEIVER(i) { - struct receiver *r = receivers + i; - if (strcmp(r->name, "afh")) - continue; - afh_recv = r; - return; - } - PARA_EMERG_LOG("fatal: afh receiver not found\n"); - exit(EXIT_FAILURE); +static unsigned *shuffle_map; + +static const char *get_playlist_file(unsigned idx) +{ + return lls_input(shuffle_map[idx], play_lpr); } -__noreturn static void print_help_and_die(void) +static void handle_help_flags(void) { - struct ggo_help help = DEFINE_GGO_HELP(play); - unsigned flags = conf.detailed_help_given? - GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS; + char *help; - ggo_print_help(&help, flags); - printf("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS); - exit(0); + if (OPT_GIVEN(DETAILED_HELP)) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP)) + help = lls_short_help(CMD_PTR); + else + return; + printf("%s\n", help); + free(help); + exit(EXIT_SUCCESS); } static void parse_config_or_die(int argc, char *argv[]) { - int i, ret; - char *config_file; - struct play_cmdline_parser_params params = { - .override = 0, - .initialize = 1, - .check_required = 0, - .check_ambiguity = 0, - .print_errors = 1 - }; + const struct lls_command *cmd = CMD_PTR; + int i, ret, cf_argc; + char *cf, *errctx, **cf_argv; + struct lls_parse_result *cf_lpr, *merged_lpr; + unsigned num_kmas; + void *map; + size_t sz; - play_cmdline_parser_ext(argc, argv, &conf, ¶ms); - loglevel = get_loglevel_by_name(conf.loglevel_arg); - version_handle_flag("play", conf.version_given); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); - if (conf.config_file_given) - config_file = para_strdup(conf.config_file_arg); + ret = lls(lls_parse(argc, argv, cmd, &play_lpr, &errctx)); + if (ret < 0) + goto fail; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + version_handle_flag("play", OPT_GIVEN(VERSION)); + handle_help_flags(); + if (OPT_GIVEN(CONFIG_FILE)) + cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE)); else { char *home = para_homedir(); - config_file = make_message("%s/.paraslash/play.conf", home); + cf = make_message("%s/.paraslash/play.conf", home); free(home); } - ret = file_exists(config_file); - if (conf.config_file_given && !ret) { - PARA_EMERG_LOG("can not read config file %s\n", config_file); - goto err; - } - if (ret) { - params.initialize = 0; - params.check_required = 1; - play_cmdline_parser_config_file(config_file, &conf, ¶ms); - loglevel = get_loglevel_by_name(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; + ret = 0; + goto free_cf; } - for (i = 0; i < conf.key_map_given; i++) { - char *kma = conf.key_map_arg[i]; + 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, &cf_lpr, &errctx)); + lls_free_argv(cf_argv); + if (ret < 0) + goto free_cf; + ret = lls(lls_merge(play_lpr, cf_lpr, cmd, &merged_lpr, &errctx)); + lls_free_parse_result(cf_lpr, cmd); + if (ret < 0) + goto free_cf; + lls_free_parse_result(play_lpr, cmd); + play_lpr = merged_lpr; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + + ret = lls(lls_check_arg_count(play_lpr, 1, INT_MAX, &errctx)); + if (ret < 0) + goto free_cf; + num_kmas = OPT_GIVEN(KEY_MAP); + for (i = 0; i < num_kmas; i++) { + const char *kma = lls_string_val(i, OPT_RESULT(KEY_MAP)); if (*kma && strchr(kma + 1, ':')) continue; PARA_EMERG_LOG("invalid key map arg: %s\n", kma); - goto err; + goto free_cf; } - free(config_file); - return; -err: - free(config_file); + ret = 1; +free_cf: + free(cf); + if (ret >= 0) + return; + lls_free_parse_result(play_lpr, cmd); +fail: + if (errctx) + PARA_EMERG_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } -static char get_playback_state(struct play_task *pt) +static char get_playback_state(void) { switch (pt->rq) { case CRT_NONE: return pt->playing? 'P' : 'U'; @@ -207,9 +238,9 @@ static char get_playback_state(struct play_task *pt) assert(false); }; -static long unsigned get_play_time(struct play_task *pt) +static long unsigned get_play_time(void) { - char state = get_playback_state(pt); + char state = get_playback_state(); long unsigned result; if (state != 'P' && state != 'U') @@ -230,17 +261,18 @@ static long unsigned get_play_time(struct play_task *pt) return result; } -static void wipe_receiver_node(struct play_task *pt) + +static void wipe_receiver_node(void) { PARA_NOTICE_LOG("cleaning up receiver node\n"); btr_remove_node(&pt->rn.btrn); - afh_recv->close(&pt->rn); - afh_recv->free_config(pt->rn.conf); + AFH_RECV->close(&pt->rn); + lls_free_parse_result(pt->rn.lpr, AFH_RECV_CMD); memset(&pt->rn, 0, sizeof(struct receiver_node)); } /* returns: 0 not eof, 1: eof, < 0: fatal error. */ -static int get_playback_error(struct play_task *pt) +static int get_playback_error(void) { int err; @@ -259,22 +291,23 @@ static int get_playback_error(struct play_task *pt) return err; } -static int eof_cleanup(struct play_task *pt) +static int eof_cleanup(void) { - struct writer *w = writers + DEFAULT_WRITER; - const struct filter *decoder = filter_get(pt->fn.filter_num); + const struct filter *decoder; + const struct writer *w = writer_get(-1); /* default writer */ int ret; - ret = get_playback_error(pt); + ret = get_playback_error(); if (ret == 0) return ret; PARA_NOTICE_LOG("cleaning up wn/fn nodes\n"); task_reap(&pt->wn.task); w->close(&pt->wn); btr_remove_node(&pt->wn.btrn); - w->free_config(pt->wn.conf); + lls_free_parse_result(pt->wn.lpr, WRITE_CMD(pt->wn.wid)); memset(&pt->wn, 0, sizeof(struct writer_node)); + decoder = filter_get(pt->fn.filter_num); task_reap(&pt->fn.task); if (decoder->close) decoder->close(&pt->fn); @@ -290,7 +323,7 @@ static int eof_cleanup(struct play_task *pt) * paused. */ if (ret < 0) - wipe_receiver_node(pt); + wipe_receiver_node(); return ret; } @@ -299,34 +332,42 @@ static int shuffle_compare(__a_unused const void *a, __a_unused const void *b) return para_random(100) - 50; } -static void shuffle(char **base, size_t num) +static void init_shuffle_map(void) { + unsigned n, num_inputs = lls_num_inputs(play_lpr); + shuffle_map = para_malloc(num_inputs * sizeof(unsigned)); + for (n = 0; n < num_inputs; n++) + shuffle_map[n] = n; + if (!OPT_GIVEN(RANDOMIZE)) + return; srandom(time(NULL)); - qsort(base, num, sizeof(char *), shuffle_compare); + qsort(shuffle_map, num_inputs, sizeof(unsigned), shuffle_compare); } static struct btr_node *new_recv_btrn(struct receiver_node *rn) { return btr_new_node(&(struct btr_node_description) - EMBRACE(.name = afh_recv->name, .context = rn, - .handler = afh_recv->execute)); + EMBRACE(.name = lls_command_name(AFH_RECV_CMD), .context = rn, + .handler = AFH_RECV->execute)); } -static int open_new_file(struct play_task *pt) +static int open_new_file(void) { int ret; - char *tmp, *path = conf.inputs[pt->next_file], *afh_recv_conf[] = - {"play", "-f", path, "-b", "0", NULL}; + const char *path = get_playlist_file(pt->next_file); + char *tmp = para_strdup(path), *errctx; + char *argv[] = {"play", "-f", tmp, "-b", "0", NULL}; PARA_NOTICE_LOG("next file: %s\n", path); - wipe_receiver_node(pt); + wipe_receiver_node(); pt->start_chunk = 0; pt->rn.btrn = new_recv_btrn(&pt->rn); - pt->rn.conf = afh_recv->parse_config(ARRAY_SIZE(afh_recv_conf) - 1, - afh_recv_conf); - assert(pt->rn.conf); - pt->rn.receiver = afh_recv; - ret = afh_recv->open(&pt->rn); + ret = lls(lls_parse(ARRAY_SIZE(argv) - 1, argv, AFH_RECV_CMD, + &pt->rn.lpr, &errctx)); + free(tmp); + assert(ret >= 0); + pt->rn.receiver = AFH_RECV; + ret = AFH_RECV->open(&pt->rn); if (ret < 0) { PARA_ERROR_LOG("could not open %s\n", path); goto fail; @@ -358,20 +399,21 @@ static int open_new_file(struct play_task *pt) } return 1; fail: - wipe_receiver_node(pt); + wipe_receiver_node(); return ret; } -static int load_file(struct play_task *pt) +static int load_file(void) { const char *af; char *tmp, buf[20]; int ret; const struct filter *decoder; + static struct lls_parse_result *filter_lpr, *writer_lpr; btr_remove_node(&pt->rn.btrn); if (!pt->rn.receiver || pt->next_file != pt->current_file) { - ret = open_new_file(pt); + ret = open_new_file(); if (ret < 0) return ret; } else { @@ -387,30 +429,31 @@ static int load_file(struct play_task *pt) /* set up decoding filter */ af = audio_format_name(pt->audio_format_num); tmp = make_message("%sdec", af); - PARA_INFO_LOG("decoder: %s\n", tmp); - ret = check_filter_arg(tmp, &pt->fn.conf); + ret = filter_setup(tmp, &pt->fn.conf, &filter_lpr); freep(&tmp); if (ret < 0) goto fail; pt->fn.filter_num = ret; + pt->fn.lpr = filter_lpr; decoder = filter_get(ret); pt->fn.btrn = btr_new_node(&(struct btr_node_description) - EMBRACE(.name = decoder->name, .parent = pt->rn.btrn, - .handler = decoder->execute, .context = &pt->fn)); + EMBRACE(.name = filter_name(pt->fn.filter_num), + .parent = pt->rn.btrn, .handler = decoder->execute, + .context = &pt->fn)); if (decoder->open) decoder->open(&pt->fn); PARA_INFO_LOG("buffer tree:\n"); btr_log_tree(pt->rn.btrn, LL_INFO); /* setup default writer */ - pt->wn.conf = check_writer_arg_or_die(NULL, &pt->wn.writer_num); - + pt->wn.wid = check_writer_arg_or_die(NULL, &writer_lpr); + pt->wn.lpr = writer_lpr; /* success, register tasks */ pt->rn.task = task_register( &(struct task_info) { - .name = afh_recv->name, - .pre_select = afh_recv->pre_select, - .post_select = afh_recv->post_select, + .name = lls_command_name(AFH_RECV_CMD), + .pre_select = AFH_RECV->pre_select, + .post_select = AFH_RECV->post_select, .context = &pt->rn }, &sched); sprintf(buf, "%s decoder", af); @@ -424,36 +467,37 @@ static int load_file(struct play_task *pt) register_writer_node(&pt->wn, pt->fn.btrn, &sched); return 1; fail: - wipe_receiver_node(pt); + wipe_receiver_node(); return ret; } -static int next_valid_file(struct play_task *pt) +static int next_valid_file(void) { int i, j = pt->current_file; + unsigned num_inputs = lls_num_inputs(play_lpr); - FOR_EACH_PLAYLIST_FILE(i) { - j = (j + 1) % conf.inputs_num; + for (i = 0; i < num_inputs; i++) { + j = (j + 1) % num_inputs; if (!pt->invalid[j]) return j; } return -E_NO_VALID_FILES; } -static int load_next_file(struct play_task *pt) +static int load_next_file(void) { int ret; again: if (pt->rq == CRT_NONE) { pt->start_chunk = 0; - ret = next_valid_file(pt); + ret = next_valid_file(); if (ret < 0) return ret; pt->next_file = ret; } else if (pt->rq == CRT_REPOS) pt->next_file = pt->current_file; - ret = load_file(pt); + ret = load_file(); if (ret < 0) { PARA_ERROR_LOG("%s: marking file as invalid\n", para_strerror(-ret)); @@ -466,7 +510,7 @@ again: return ret; } -static void kill_stream(struct play_task *pt) +static void kill_stream(void) { if (pt->wn.task) task_notify(pt->wn.task, E_EOF); @@ -475,14 +519,15 @@ static void kill_stream(struct play_task *pt) #ifdef HAVE_READLINE /* only called from com_prev(), nec. only if we have readline */ -static int previous_valid_file(struct play_task *pt) +static int previous_valid_file(void) { int i, j = pt->current_file; + unsigned num_inputs = lls_num_inputs(play_lpr); - FOR_EACH_PLAYLIST_FILE(i) { + for (i = 0; i < num_inputs; i++) { j--; if (j < 0) - j = conf.inputs_num - 1; + j = num_inputs - 1; if (!pt->invalid[j]) return j; } @@ -533,7 +578,7 @@ static const char *default_keyseqs[] = {INTERNAL_KEYMAP_ENTRIES}; static const char *default_commands[] = {INTERNAL_KEYMAP_ENTRIES}; #undef KEYMAP_ENTRY #define NUM_INTERNALLY_MAPPED_KEYS ARRAY_SIZE(default_commands) -#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + conf.key_map_given) +#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + OPT_GIVEN(KEY_MAP)) #define FOR_EACH_MAPPED_KEY(i) for (i = 0; i < NUM_MAPPED_KEYS; i++) static inline bool is_internal_key(int key) @@ -564,9 +609,9 @@ static inline int get_key_map_idx(int key) get_internal_key_map_idx(key) : get_user_key_map_idx(key); } -static inline char *get_user_key_map_arg(int key) +static inline const char *get_user_key_map_arg(int key) { - return conf.key_map_arg[get_user_key_map_idx(key)]; + return lls_string_val(get_user_key_map_idx(key), OPT_RESULT(KEY_MAP)); } static inline char *get_internal_key_map_seq(int key) @@ -653,24 +698,6 @@ static char **get_mapped_keyseqs(void) return result; } -#include "play.command_list.h" - -typedef int play_command_handler_t(struct play_task *, int, char**); -static play_command_handler_t PLAY_COMMAND_HANDLERS; - -/* defines one command of para_play */ -struct pp_command { - const char *name; - play_command_handler_t *handler; - const char *description; - const char *usage; - const char *help; -}; - -static struct pp_command pp_cmds[] = {DEFINE_PLAY_CMD_ARRAY}; -#define FOR_EACH_COMMAND(c) for (c = 0; pp_cmds[c].name; c++) - -#include "play.completion.h" static struct i9e_completer pp_completers[]; I9E_DUMMY_COMPLETER(jmp); @@ -682,7 +709,6 @@ I9E_DUMMY_COMPLETER(ls); I9E_DUMMY_COMPLETER(info); I9E_DUMMY_COMPLETER(play); I9E_DUMMY_COMPLETER(pause); -I9E_DUMMY_COMPLETER(stop); I9E_DUMMY_COMPLETER(tasks); I9E_DUMMY_COMPLETER(quit); I9E_DUMMY_COMPLETER(ff); @@ -693,9 +719,16 @@ static void help_completer(struct i9e_completion_info *ci, result->matches = i9e_complete_commands(ci->word, pp_completers); } -static struct i9e_completer pp_completers[] = {PLAY_COMPLETERS {.name = NULL}}; +I9E_DUMMY_COMPLETER(SUPERCOMMAND_UNAVAILABLE); +static struct i9e_completer pp_completers[] = { +#define LSG_PLAY_CMD_CMD(_name) {.name = #_name, \ + .completer = _name ## _completer} + LSG_PLAY_CMD_SUBCOMMANDS +#undef LSG_PLAY_CMD_CMD + {.name = NULL} +}; -static void attach_stdout(struct play_task *pt, const char *name) +static void attach_stdout(const char *name) { if (pt->btrn) return; @@ -704,141 +737,137 @@ static void attach_stdout(struct play_task *pt, const char *name) i9e_attach_to_stdout(pt->btrn); } -static void detach_stdout(struct play_task *pt) +static void detach_stdout(void) { btr_remove_node(&pt->btrn); } -static int com_quit(struct play_task *pt, int argc, __a_unused char **argv) +static int com_quit(__a_unused struct lls_parse_result *lpr) { - if (argc != 1) - return -E_PLAY_SYNTAX; pt->rq = CRT_TERM_RQ; return 0; } +EXPORT_PLAY_CMD_HANDLER(quit); -static int com_help(struct play_task *pt, int argc, char **argv) +static int com_help(struct lls_parse_result *lpr) { - int i; - char *buf; + int i, ret; + char *buf, *errctx; size_t sz; + const struct lls_command *cmd; - if (argc > 2) - return -E_PLAY_SYNTAX; - if (argc < 2) { - if (pt->background) - FOR_EACH_COMMAND(i) { - sz = xasprintf(&buf, "%s\t%s\n", pp_cmds[i].name, - pp_cmds[i].description); - btr_add_output(buf, sz, pt->btrn); - } - else { - FOR_EACH_MAPPED_KEY(i) { - bool internal = is_internal_key(i); - int idx = get_key_map_idx(i); - char *seq = get_key_map_seq_safe(i); - char *cmd = get_key_map_cmd(i); - sz = xasprintf(&buf, - "%s key #%d: %s -> %s\n", - internal? "internal" : "user-defined", - idx, seq, cmd); + ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx)); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + return ret; + } + if (lls_num_inputs(lpr) == 0) { + if (pt->background) { + for (i = 1; (cmd = lls_cmd(i, play_cmd_suite)); i++) { + sz = xasprintf(&buf, "%s\t%s\n", + lls_command_name(cmd), lls_purpose(cmd)); btr_add_output(buf, sz, pt->btrn); - free(seq); - free(cmd); } + return 0; + } + FOR_EACH_MAPPED_KEY(i) { + bool internal = is_internal_key(i); + int idx = get_key_map_idx(i); + char *seq = get_key_map_seq_safe(i); + char *kmc = get_key_map_cmd(i); + sz = xasprintf(&buf, "%s key #%d: %s -> %s\n", + internal? "internal" : "user-defined", + idx, seq, kmc); + btr_add_output(buf, sz, pt->btrn); + free(seq); + free(kmc); } return 0; } - FOR_EACH_COMMAND(i) { - if (strcmp(pp_cmds[i].name, argv[1])) - continue; - sz = xasprintf(&buf, - "NAME\n\t%s -- %s\n" - "SYNOPSIS\n\t%s\n" - "DESCRIPTION\n%s\n", - argv[1], - pp_cmds[i].description, - pp_cmds[i].usage, - pp_cmds[i].help - ); - btr_add_output(buf, sz, pt->btrn); - return 0; + ret = lls(lls_lookup_subcmd(lls_input(0, lpr), play_cmd_suite, + &errctx)); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + return ret; } - return -E_BAD_PLAY_CMD; + cmd = lls_cmd(ret, play_cmd_suite); + buf = lls_long_help(cmd); + assert(buf); + btr_add_output(buf, strlen(buf), pt->btrn); + return 0; } +EXPORT_PLAY_CMD_HANDLER(help); -static int com_info(struct play_task *pt, int argc, __a_unused char **argv) +static int com_info(__a_unused struct lls_parse_result *lpr) { char *buf; size_t sz; static char dflt[] = "[no information available]"; - if (argc != 1) - return -E_PLAY_SYNTAX; sz = xasprintf(&buf, "playlist_pos: %u\npath: %s\n", - pt->current_file, conf.inputs[pt->current_file]); + pt->current_file, get_playlist_file(pt->current_file)); btr_add_output(buf, sz, pt->btrn); buf = pt->afhi_txt? pt->afhi_txt : dflt; btr_add_output_dont_free(buf, strlen(buf), pt->btrn); return 0; } +EXPORT_PLAY_CMD_HANDLER(info); -static void list_file(struct play_task *pt, int num) +static void list_file(int num) { char *buf; size_t sz; sz = xasprintf(&buf, "%s %4d %s\n", num == pt->current_file? - "*" : " ", num, conf.inputs[num]); + "*" : " ", num, get_playlist_file(num)); btr_add_output(buf, sz, pt->btrn); } -static int com_tasks(struct play_task *pt, int argc, __a_unused char **argv) +static int com_tasks(__a_unused struct lls_parse_result *lpr) { static char state; char *buf; size_t sz; - if (argc != 1) - return -E_PLAY_SYNTAX; - buf = get_task_list(&sched); btr_add_output(buf, strlen(buf), pt->btrn); - state = get_playback_state(pt); + state = get_playback_state(); sz = xasprintf(&buf, "state: %c\n", state); btr_add_output(buf, sz, pt->btrn); return 0; } +EXPORT_PLAY_CMD_HANDLER(tasks); -static int com_ls(struct play_task *pt, int argc, char **argv) +static int com_ls(__a_unused struct lls_parse_result *lpr) { - int i, j, ret; + int i; + unsigned num_inputs = lls_num_inputs(play_lpr); - if (argc == 1) { - FOR_EACH_PLAYLIST_FILE(i) - list_file(pt, i); - return 0; - } - for (j = 1; j < argc; j++) { - FOR_EACH_PLAYLIST_FILE(i) { - ret = fnmatch(argv[j], conf.inputs[i], 0); - if (ret == 0) /* match */ - list_file(pt, i); - } - } + for (i = 0; i < num_inputs; i++) + list_file(i); return 0; } +EXPORT_PLAY_CMD_HANDLER(ls); -static int com_play(struct play_task *pt, int argc, char **argv) +static int com_play(struct lls_parse_result *lpr) { int32_t x; int ret; - char state; + char state, *errctx; - if (argc > 2) - return -E_PLAY_SYNTAX; - state = get_playback_state(pt); - if (argc == 1) { + ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx)); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + return ret; + } + state = get_playback_state(); + if (lls_num_inputs(lpr) == 0) { if (state == 'P') return 0; pt->next_file = pt->current_file; @@ -846,29 +875,28 @@ static int com_play(struct play_task *pt, int argc, char **argv) pt->playing = true; return 0; } - ret = para_atoi32(argv[1], &x); + ret = para_atoi32(lls_input(0, lpr), &x); if (ret < 0) return ret; - if (x < 0 || x >= conf.inputs_num) + if (x < 0 || x >= lls_num_inputs(play_lpr)) return -ERRNO_TO_PARA_ERROR(EINVAL); - kill_stream(pt); + kill_stream(); pt->next_file = x; pt->rq = CRT_FILE_CHANGE; return 0; } +EXPORT_PLAY_CMD_HANDLER(play); -static int com_pause(struct play_task *pt, int argc, __a_unused char **argv) +static int com_pause(__a_unused struct lls_parse_result *lpr) { char state; long unsigned seconds, ss; - if (argc != 1) - return -E_PLAY_SYNTAX; - state = get_playback_state(pt); + state = get_playback_state(); pt->playing = false; if (state != 'P') return 0; - seconds = get_play_time(pt); + seconds = get_play_time(); pt->playing = false; ss = 0; if (pt->seconds > 0) @@ -876,96 +904,105 @@ static int com_pause(struct play_task *pt, int argc, __a_unused char **argv) ss = PARA_MAX(ss, 0UL); ss = PARA_MIN(ss, pt->num_chunks); pt->start_chunk = ss; - kill_stream(pt); + kill_stream(); return 0; } +EXPORT_PLAY_CMD_HANDLER(pause); -static int com_prev(struct play_task *pt, int argc, __a_unused char **argv) - +static int com_prev(__a_unused struct lls_parse_result *lpr) { int ret; - if (argc != 1) - return -E_PLAY_SYNTAX; - ret = previous_valid_file(pt); + ret = previous_valid_file(); if (ret < 0) return ret; - kill_stream(pt); + kill_stream(); pt->next_file = ret; pt->rq = CRT_FILE_CHANGE; pt->start_chunk = 0; return 0; } +EXPORT_PLAY_CMD_HANDLER(prev); -static int com_next(struct play_task *pt, int argc, __a_unused char **argv) +static int com_next(__a_unused struct lls_parse_result *lpr) { int ret; - if (argc != 1) - return -E_PLAY_SYNTAX; - ret = next_valid_file(pt); + ret = next_valid_file(); if (ret < 0) return ret; - kill_stream(pt); + kill_stream(); pt->next_file = ret; pt->rq = CRT_FILE_CHANGE; pt->start_chunk = 0; return 0; } +EXPORT_PLAY_CMD_HANDLER(next); -static int com_fg(struct play_task *pt, int argc, __a_unused char **argv) +static int com_fg(__a_unused struct lls_parse_result *lpr) { - if (argc != 1) - return -E_PLAY_SYNTAX; pt->background = false; return 0; } +EXPORT_PLAY_CMD_HANDLER(fg); -static int com_bg(struct play_task *pt, int argc, __a_unused char **argv) +static int com_bg(__a_unused struct lls_parse_result *lpr) { - if (argc != 1) - return -E_PLAY_SYNTAX; pt->background = true; return 0; } +EXPORT_PLAY_CMD_HANDLER(bg); -static int com_jmp(struct play_task *pt, int argc, char **argv) +static int com_jmp(struct lls_parse_result *lpr) { int32_t percent; int ret; + char *errctx; - if (argc != 2) - return -E_PLAY_SYNTAX; - ret = para_atoi32(argv[1], &percent); + ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + return ret; + } + ret = para_atoi32(lls_input(0, lpr), &percent); if (ret < 0) return ret; if (percent < 0 || percent > 100) return -ERRNO_TO_PARA_ERROR(EINVAL); if (percent == 100) - return com_next(pt, 1, (char *[]){"next", NULL}); + return com_next(NULL); if (pt->playing && !pt->fn.btrn) return 0; pt->start_chunk = percent * pt->num_chunks / 100; if (!pt->playing) return 0; pt->rq = CRT_REPOS; - kill_stream(pt); + kill_stream(); return 0; } +EXPORT_PLAY_CMD_HANDLER(jmp); -static int com_ff(struct play_task *pt, int argc, char **argv) +static int com_ff(struct lls_parse_result *lpr) { int32_t seconds; + char *errctx; int ret; - if (argc != 2) - return -E_PLAY_SYNTAX; - ret = para_atoi32(argv[1], &seconds); + ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + return ret; + } + ret = para_atoi32(lls_input(0, lpr), &seconds); if (ret < 0) return ret; if (pt->playing && !pt->fn.btrn) return 0; - seconds += get_play_time(pt); + seconds += get_play_time(); seconds = PARA_MIN(seconds, (typeof(seconds))pt->seconds - 4); seconds = PARA_MAX(seconds, 0); pt->start_chunk = pt->num_chunks * seconds / pt->seconds; @@ -974,49 +1011,52 @@ static int com_ff(struct play_task *pt, int argc, char **argv) if (!pt->playing) return 0; pt->rq = CRT_REPOS; - kill_stream(pt); + kill_stream(); return 0; } +EXPORT_PLAY_CMD_HANDLER(ff); -static int run_command(char *line, struct play_task *pt) +static int run_command(char *line) { - int i, ret, argc; + int ret, argc; char **argv = NULL; + char *errctx = NULL; + const struct play_command_info *pci; + struct lls_parse_result *lpr; + const struct lls_command *cmd; - attach_stdout(pt, __FUNCTION__); + attach_stdout(__FUNCTION__); ret = create_argv(line, " ", &argv); - if (ret < 0) { - PARA_ERROR_LOG("parse error: %s\n", para_strerror(-ret)); - return 0; - } + if (ret < 0) + goto out; if (ret == 0) goto out; argc = ret; - FOR_EACH_COMMAND(i) { - if (strcmp(pp_cmds[i].name, argv[0])) - continue; - ret = pp_cmds[i].handler(pt, argc, argv); - if (ret < 0) - PARA_WARNING_LOG("%s: %s\n", pt->background? - "" : argv[0], para_strerror(-ret)); - ret = 1; + ret = lls(lls_lookup_subcmd(argv[0], play_cmd_suite, &errctx)); + if (ret < 0) goto out; - } - PARA_WARNING_LOG("invalid command: %s\n", argv[0]); - ret = 0; + cmd = lls_cmd(ret, play_cmd_suite); + ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx)); + if (ret < 0) + goto out; + pci = lls_user_data(cmd); + ret = pci->handler(lpr); + lls_free_parse_result(lpr, cmd); out: + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); free_argv(argv); return ret; } static int play_i9e_line_handler(char *line) { - return run_command(line, &play_task); + return run_command(line); } static int play_i9e_key_handler(int key) { - struct play_task *pt = &play_task; int idx = get_key_map_idx(key); char *seq = get_key_map_seq(key); char *cmd = get_key_map_cmd(key); @@ -1025,7 +1065,7 @@ static int play_i9e_key_handler(int key) PARA_NOTICE_LOG("pressed %d: %s key #%d (%s -> %s)\n", key, internal? "internal" : "user-defined", idx, seq, cmd); - run_command(cmd, pt); + run_command(cmd); free(seq); free(cmd); pt->next_update = *now; @@ -1042,7 +1082,7 @@ static struct i9e_client_info ici = { static void sigint_handler(int sig) { - play_task.background = true; + pt->background = true; i9e_signal_dispatch(sig); } @@ -1051,15 +1091,15 @@ static void sigint_handler(int sig) * stderr. Once the i9e subsystem has been initialized, we switch to the i9e * log facility. */ -static void session_open(struct play_task *pt) +static void session_open(void) { int ret; char *history_file; struct sigaction act; PARA_NOTICE_LOG("\n%s\n", version_text("play")); - if (conf.history_file_given) - history_file = para_strdup(conf.history_file_arg); + if (OPT_GIVEN(HISTORY_FILE)) + history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE)); else { char *home = para_homedir(); history_file = make_message("%s/.paraslash/play.history", @@ -1095,7 +1135,7 @@ out: exit(EXIT_FAILURE); } -static void session_update_time_string(struct play_task *pt, char *str, unsigned len) +static void session_update_time_string(char *str, unsigned len) { if (pt->background) return; @@ -1120,30 +1160,30 @@ static void session_update_time_string(struct play_task *pt, char *str, unsigned * terminates. Subsequent calls to i9e_get_error() then return negative and we * are allowed to call i9e_close() and terminate as well. */ -static int session_post_select(__a_unused struct sched *s, struct play_task *pt) +static int session_post_select(__a_unused struct sched *s) { int ret; if (pt->background) - detach_stdout(pt); + detach_stdout(); else - attach_stdout(pt, __FUNCTION__); + attach_stdout(__FUNCTION__); ret = i9e_get_error(); if (ret < 0) { - kill_stream(pt); + kill_stream(); i9e_close(); para_log = stderr_log; free(ici.history_file); return ret; } - if (get_playback_state(pt) == 'X') + if (get_playback_state() == 'X') i9e_signal_dispatch(SIGTERM); return 0; } #else /* HAVE_READLINE */ -static int session_post_select(struct sched *s, struct play_task *pt) +static int session_post_select(struct sched *s) { char c; @@ -1151,38 +1191,36 @@ static int session_post_select(struct sched *s, struct play_task *pt) return 0; if (read(STDIN_FILENO, &c, 1)) do_nothing; - kill_stream(pt); + kill_stream(); return 1; } -static void session_open(__a_unused struct play_task *pt) +static void session_open(void) { } -static void session_update_time_string(__a_unused struct play_task *pt, - char *str, __a_unused unsigned len) +static void session_update_time_string(char *str, __a_unused unsigned len) { printf("\r%s ", str); fflush(stdout); } #endif /* HAVE_READLINE */ -static void play_pre_select(struct sched *s, void *context) +static void play_pre_select(struct sched *s, __a_unused void *context) { - struct play_task *pt = context; char state; para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno); - state = get_playback_state(pt); + state = get_playback_state(); if (state == 'R' || state == 'F' || state == 'X') return sched_min_delay(s); sched_request_barrier_or_min_delay(&pt->next_update, s); } -static unsigned get_time_string(struct play_task *pt, char **result) +static unsigned get_time_string(char **result) { int seconds, length; - char state = get_playback_state(pt); + char state = get_playback_state(); /* do not return anything if things are about to change */ if (state != 'P' && state != 'U') { @@ -1192,7 +1230,7 @@ static unsigned get_time_string(struct play_task *pt, char **result) length = pt->seconds; if (length == 0) return xasprintf(result, "0:00 [0:00] (0%%/0:00)"); - seconds = get_play_time(pt); + seconds = get_play_time(); return xasprintf(result, "#%u: %d:%02d [%d:%02d] (%d%%/%d:%02d) %s", pt->current_file, seconds / 60, @@ -1202,28 +1240,27 @@ static unsigned get_time_string(struct play_task *pt, char **result) length? (seconds * 100 + length / 2) / length : 0, length / 60, length % 60, - conf.inputs[pt->current_file] + get_playlist_file(pt->current_file) ); } -static int play_post_select(struct sched *s, void *context) +static int play_post_select(struct sched *s, __a_unused void *context) { - struct play_task *pt = context; int ret; - ret = eof_cleanup(pt); + ret = eof_cleanup(); if (ret < 0) { pt->rq = CRT_TERM_RQ; return 0; } - ret = session_post_select(s, pt); + ret = session_post_select(s); if (ret < 0) goto out; if (!pt->wn.btrn && !pt->fn.btrn) { - char state = get_playback_state(pt); + char state = get_playback_state(); if (state == 'P' || state == 'R' || state == 'F') { PARA_NOTICE_LOG("state: %c\n", state); - ret = load_next_file(pt); + ret = load_next_file(); if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); pt->rq = CRT_TERM_RQ; @@ -1235,10 +1272,10 @@ static int play_post_select(struct sched *s, void *context) } if (tv_diff(now, &pt->next_update, NULL) >= 0) { char *str; - unsigned len = get_time_string(pt, &str); + unsigned len = get_time_string(&str); struct timeval delay = {.tv_sec = 0, .tv_usec = 100 * 1000}; if (str && len > 0) - session_update_time_string(pt, str, len); + session_update_time_string(str, len); free(str); tv_add(now, &delay, &pt->next_update); } @@ -1258,26 +1295,20 @@ out: int main(int argc, char *argv[]) { int ret; - struct play_task *pt = &play_task; + unsigned num_inputs; /* needed this early to make help work */ recv_init(); - filter_init(); - writer_init(); sched.default_timeout.tv_sec = 5; - parse_config_or_die(argc, argv); - if (conf.inputs_num == 0) - print_help_and_die(); - check_afh_receiver_or_die(); - - session_open(pt); - if (conf.randomize_given) - shuffle(conf.inputs, conf.inputs_num); - pt->invalid = para_calloc(sizeof(*pt->invalid) * conf.inputs_num); + AFH_RECV->init(); + session_open(); + num_inputs = lls_num_inputs(play_lpr); + init_shuffle_map(); + pt->invalid = para_calloc(sizeof(*pt->invalid) * num_inputs); pt->rq = CRT_FILE_CHANGE; - pt->current_file = conf.inputs_num - 1; + pt->current_file = num_inputs - 1; pt->playing = true; pt->task = task_register(&(struct task_info){ .name = "play", diff --git a/play.cmd b/play.cmd deleted file mode 100644 index 459ad8cf..00000000 --- a/play.cmd +++ /dev/null @@ -1,77 +0,0 @@ -BN: play -SF: play.c -SN: list of commands ---- -N: help -D: Display command list or help for given command. -U: help [command] -H: This command acts differently depending on whether it is executed in command -H: mode or in insert mode. In command mode, the list of keybindings is printed. -H: In insert mode, if no command is given, the list of commands is shown. -H: Otherwise, the help for the given command is printed. ---- -N: next -D: Load next file. -U: next -H: Closes the current file and loads the next file of the playlist. ---- -N: prev -D: Load previous file. -U: prev -H: Closes the current file and loads the previous file of the playlist. ---- -N: fg -D: Enter command mode. -U: fg -H: In this mode, file name and play time are displayed. Hit CTRL+C to switch to -H: input mode. ---- -N: bg -D: Enter input mode. -U: bg -H: Only useful if called in command mode via a key binding. The default key -H: bindings map this command to the colon key, so pressing : in command mode -H: activates insert mode. ---- -N: jmp -D: Jump to position in current file. -U: jmp -H: The argument should be an integer between 0 and 100. ---- -N: ff -D: Jump forwards or backwards. -U: ff -H: Negative values mean to jmp backwards the given amount of seconds. ---- -N: ls -D: List playlist. -U: ls -H: This prints all paths of the playlist. The currently active file is -H: marked with an asterisk. ---- -N: info -D: Print information about the current file. -U: info -H: This is the audio file selector info. ---- -N: play -D: Start or resume playing. -U: play [] -H: Without , starts playing at the current position. Otherwise, the -H: corresponding file is loaded and playback is started. ---- -N: pause -D: Stop playing. -U: pause -H: When paused, it is still possible to jump around in the file via the jmp and ff -H: comands. ---- -N: tasks -D: Print list of active tasks. -U: tasks -H: Mainly useful for debugging. ---- -N: quit -D: Exit para_play. -U: quit -H: Pressing CTRL+D causes EOF on stdin which also exits para_play. diff --git a/playlist.c b/playlist.c index 8ea1854b..b9e52c75 100644 --- a/playlist.c +++ b/playlist.c @@ -6,6 +6,7 @@ #include #include +#include #include "para.h" #include "error.h" diff --git a/prebuffer_filter.c b/prebuffer_filter.c index 6078da07..8ca1630d 100644 --- a/prebuffer_filter.c +++ b/prebuffer_filter.c @@ -7,12 +7,12 @@ /** \file prebuffer_filter.c Paraslash's prebuffering filter. */ #include +#include #include "para.h" -#include "prebuffer_filter.cmdline.h" +#include "filter_cmd.lsg.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "string.h" @@ -20,8 +20,6 @@ /** Data specific to the prebuffer filter. */ struct private_prebuffer_data { - /** The configuration data for this instance of the filter. */ - struct prebuffer_filter_args_info *conf; /** Number of bytes prebuffered or -1 if no longer prebuffering. */ int prebuffered; /** End of prebuffering period. */ @@ -34,16 +32,16 @@ static void prebuffer_pre_select(struct sched *s, void *context) struct btr_node *btrn = fn->btrn; size_t iqs = btr_get_input_queue_size(btrn); struct private_prebuffer_data *ppd = fn->private_data; - struct prebuffer_filter_args_info *conf = ppd->conf; struct timeval diff; if (iqs == 0) return; if (ppd->barrier.tv_sec == 0) { + uint32_t duration = FILTER_CMD_OPT_UINT32_VAL(PREBUFFER, + DURATION, fn->lpr); struct timeval tv; - PARA_INFO_LOG("prebuffer period %dms\n", - conf->duration_arg); - ms2tv(conf->duration_arg, &tv); + PARA_INFO_LOG("prebuffer period %" PRIu32 "ms\n", duration); + ms2tv(duration, &tv); tv_add(&tv, now, &ppd->barrier); } if (tv_diff(&ppd->barrier, now, &diff) < 0) @@ -62,66 +60,27 @@ static int prebuffer_post_select(__a_unused struct sched *s, void *context) struct btr_node *btrn = fn->btrn; size_t iqs = btr_get_input_queue_size(btrn); struct private_prebuffer_data *ppd = fn->private_data; - struct prebuffer_filter_args_info *conf = ppd->conf; + uint32_t size = FILTER_CMD_OPT_UINT32_VAL(PREBUFFER, SIZE, fn->lpr); if (ppd->barrier.tv_sec == 0) return 0; if (tv_diff(now, &ppd->barrier, NULL) < 0) return 0; - if (iqs < conf->size_arg) + if (iqs < size) return 0; btr_splice_out_node(&fn->btrn); return -E_PREBUFFER_SUCCESS; } -static int prebuffer_parse_config(int argc, char **argv, void **config) -{ - struct prebuffer_filter_args_info *conf = para_calloc(sizeof(*conf)); - int ret; - - prebuffer_filter_cmdline_parser(argc, argv, conf); - ret = -ERRNO_TO_PARA_ERROR(EINVAL); - if (conf->duration_arg < 0) - goto err; - if (conf->size_arg < 0) - goto err; - PARA_NOTICE_LOG("prebuffering %ims, %i bytes\n", conf->duration_arg, - conf->size_arg); - *config = conf; - return 1; -err: - free(conf); - return ret; -} - static void prebuffer_open(struct filter_node *fn) { struct private_prebuffer_data *ppd = para_calloc(sizeof(*ppd)); - - ppd->conf = fn->conf; fn->private_data = ppd; } -static void prebuffer_free_config(void *conf) -{ - prebuffer_filter_cmdline_parser_free(conf); -} - -/** - * The init function of the prebuffer filter. - * - * \param f Pointer to the struct to initialize. - */ -void prebuffer_filter_init(struct filter *f) -{ - struct prebuffer_filter_args_info dummy; - - prebuffer_filter_cmdline_parser_init(&dummy); - f->open = prebuffer_open; - f->close = prebuffer_close; - f->parse_config = prebuffer_parse_config; - f->free_config = prebuffer_free_config; - f->pre_select = prebuffer_pre_select; - f->post_select = prebuffer_post_select; - f->help = (struct ggo_help)DEFINE_GGO_HELP(prebuffer_filter); -} +const struct filter lsg_filter_cmd_com_prebuffer_user_data = { + .open = prebuffer_open, + .close = prebuffer_close, + .pre_select = prebuffer_pre_select, + .post_select = prebuffer_post_select, +}; diff --git a/recv.c b/recv.c index 9de3033f..9842c7dc 100644 --- a/recv.c +++ b/recv.c @@ -8,14 +8,16 @@ #include #include +#include +#include +#include "recv_cmd.lsg.h" +#include "recv.lsg.h" #include "para.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "recv.h" -#include "recv.cmdline.h" #include "fd.h" #include "string.h" #include "error.h" @@ -25,26 +27,31 @@ /** Array of error strings. */ DEFINE_PARA_ERRLIST; -extern void afh_recv_init(struct receiver *r); -#undef AFH_RECEIVER -#define AFH_RECEIVER {.name = "afh", .init = afh_recv_init}, -DEFINE_RECEIVER_ARRAY; - -/** The gengetopt args info struct. */ -static struct recv_args_info conf; +#define CMD_PTR (lls_cmd(0, recv_suite)) +#define OPT_RESULT(_name, _lpr) \ + (lls_opt_result(LSG_RECV_PARA_RECV_OPT_ ## _name, lpr)) +#define OPT_GIVEN(_name, _lpr) (lls_opt_given(OPT_RESULT(_name, _lpr))) +#define OPT_UINT32_VAL(_name, _lpr) (lls_uint32_val(0, OPT_RESULT(_name, _lpr))) +#define OPT_STRING_VAL(_name, _lpr) (lls_string_val(0, OPT_RESULT(_name, _lpr))) static int loglevel; /** Always log to stderr. */ INIT_STDERR_LOGGING(loglevel); -__noreturn static void print_help_and_die(void) +static void handle_help_flag(struct lls_parse_result *lpr) { - struct ggo_help h = DEFINE_GGO_HELP(recv); - bool d = conf.detailed_help_given; - - ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS); - print_receiver_helps(d? GPH_MODULE_FLAGS_DETAILED : GPH_MODULE_FLAGS); - exit(0); + char *help; + + if (OPT_GIVEN(DETAILED_HELP, lpr)) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP, lpr)) + help = lls_short_help(CMD_PTR); + else + return; + printf("%s\n", help); + free(help); + print_receiver_helps(OPT_GIVEN(DETAILED_HELP, lpr)); + exit(EXIT_SUCCESS); } /** @@ -60,41 +67,42 @@ __noreturn static void print_help_and_die(void) */ int main(int argc, char *argv[]) { - int ret, r_opened = 0, receiver_num; - struct receiver *r = NULL; + int ret; + const struct receiver *r = NULL; struct receiver_node rn; struct stdout_task sot = {.btrn = NULL}; static struct sched s; struct task_info ti; + const struct lls_command *cmd; + struct lls_parse_result *lpr; /* command line */ + struct lls_parse_result *receiver_lpr; /* receiver specific options */ + char *errctx; - recv_cmdline_parser(argc, argv, &conf); - loglevel = get_loglevel_by_name(conf.loglevel_arg); - version_handle_flag("recv", conf.version_given); + ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx)); + if (ret < 0) + goto out; + loglevel = OPT_UINT32_VAL(LOGLEVEL, lpr); + version_handle_flag("recv", OPT_GIVEN(VERSION, lpr)); + handle_help_flag(lpr); recv_init(); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); - memset(&rn, 0, sizeof(struct receiver_node)); - rn.conf = check_receiver_arg(conf.receiver_arg, &receiver_num); - if (!rn.conf) { - PARA_EMERG_LOG("invalid receiver specifier\n"); - ret = -E_RECV_SYNTAX; - goto out; - } - r = &receivers[receiver_num]; + ret = check_receiver_arg(OPT_STRING_VAL(RECEIVER, lpr), &receiver_lpr); + if (ret < 0) + goto free_lpr; + cmd = lls_cmd(ret, recv_cmd_suite); + r = lls_user_data(cmd); rn.receiver = r; + rn.lpr = receiver_lpr; rn.btrn = btr_new_node(&(struct btr_node_description) - EMBRACE(.name = r->name)); + EMBRACE(.name = lls_command_name(cmd))); ret = r->open(&rn); if (ret < 0) - goto out; - r_opened = 1; - + goto remove_btrn; sot.btrn = btr_new_node(&(struct btr_node_description) EMBRACE(.parent = rn.btrn, .name = "stdout")); stdout_task_register(&sot, &s); - ti.name = r->name; + ti.name = lls_command_name(cmd); ti.pre_select = r->pre_select; ti.post_select = r->post_select; ti.context = &rn; @@ -104,15 +112,19 @@ int main(int argc, char *argv[]) s.default_timeout.tv_usec = 0; ret = schedule(&s); sched_shutdown(&s); -out: - if (r_opened) - r->close(&rn); - btr_remove_node(&rn.btrn); + r->close(&rn); btr_remove_node(&sot.btrn); - if (rn.conf) - r->free_config(rn.conf); - - if (ret < 0) +remove_btrn: + btr_remove_node(&rn.btrn); + lls_free_parse_result(receiver_lpr, cmd); +free_lpr: + lls_free_parse_result(lpr, CMD_PTR); +out: + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + } return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/recv.h b/recv.h index 1a0de659..68978a38 100644 --- a/recv.h +++ b/recv.h @@ -11,11 +11,11 @@ */ struct receiver_node { /** Points to the corresponding receiver. */ - struct receiver *receiver; + const struct receiver *receiver; /** Receiver-specific data. */ void *private_data; - /** Pointer to the configuration data for this instance. */ - void *conf; + /** The parsed command line options for this instance. */ + struct lls_parse_result *lpr; /** The task associated with this instance. */ struct task *task; /** The receiver node is always the root of the buffer tree. */ @@ -44,34 +44,13 @@ struct receiver_node { */ struct receiver { /** - * The name of the receiver. - */ - const char *name; - /** - * The receiver init function. + * The optional receiver init function. * - * It must fill in all other function pointers and is assumed to succeed. + * Performs any initialization needed before the receiver can be opened. * * \sa http_recv_init udp_recv_init. */ - void (*init)(struct receiver *r); - /** - * The command line parser of the receiver. - * - * It should check whether the command line options given by \a argc - * and \a argv are valid. On success, it should return a pointer to - * the receiver-specific configuration data determined by \a argc and - * \a argv. Note that this might be called more than once with - * different values of \a argc and \a argv. - */ - void *(*parse_config)(int argc, char **argv); - /** - * Deallocate the configuration structure of a receiver node. - * - * This calls the receiver-specific cleanup function generated by - * gengetopt. - */ - void (*free_config)(void *conf); + void (*init)(void); /** * Open one instance of the receiver. * @@ -117,8 +96,6 @@ struct receiver { */ int (*post_select)(struct sched *s, void *context); - /** The two help texts of this receiver. */ - struct ggo_help help; /** * Answer a buffer tree query. * @@ -128,31 +105,23 @@ struct receiver { btr_command_handler execute; }; -/** Define an array of all available receivers. */ -#define DEFINE_RECEIVER_ARRAY struct receiver receivers[] = { \ - HTTP_RECEIVER \ - DCCP_RECEIVER \ - UDP_RECEIVER \ - AFH_RECEIVER \ - {.name = NULL}}; +#define RECV_CMD(_num) (lls_cmd(_num, recv_cmd_suite)) + +#define RECV_CMD_OPT_RESULT(_recv, _opt, _lpr) \ + (lls_opt_result(LSG_RECV_CMD_ ## _recv ## _OPT_ ## _opt, _lpr)) +#define RECV_CMD_OPT_GIVEN(_recv, _opt, _lpr) \ + (lls_opt_given(RECV_CMD_OPT_RESULT(_recv, _opt, _lpr))) +#define RECV_CMD_OPT_STRING_VAL(_recv, _opt, _lpr) \ + (lls_string_val(0, RECV_CMD_OPT_RESULT(_recv, _opt, _lpr))) +#define RECV_CMD_OPT_UINT32_VAL(_recv, _opt, _lpr) \ + (lls_uint32_val(0, RECV_CMD_OPT_RESULT(_recv, _opt, _lpr))) +#define RECV_CMD_OPT_INT32_VAL(_recv, _opt, _lpr) \ + (lls_int32_val(0, RECV_CMD_OPT_RESULT(_recv, _opt, _lpr))) /** Iterate over all available receivers. */ -#define FOR_EACH_RECEIVER(i) for (i = 0; receivers[i].name; i++) +#define FOR_EACH_RECEIVER(i) for (i = 1; lls_cmd(i, recv_cmd_suite); i++) void recv_init(void); -void *check_receiver_arg(char *ra, int *receiver_num); -void print_receiver_helps(unsigned flags); +int check_receiver_arg(const char *ra, struct lls_parse_result **lprp); +void print_receiver_helps(bool detailed); int generic_recv_pre_select(struct sched *s, struct receiver_node *rn); - -/** \cond receiver */ -extern void http_recv_init(struct receiver *r); -#define HTTP_RECEIVER {.name = "http", .init = http_recv_init}, -extern void dccp_recv_init(struct receiver *r); -#define DCCP_RECEIVER {.name = "dccp", .init = dccp_recv_init}, -extern void udp_recv_init(struct receiver *r); -#define UDP_RECEIVER {.name = "udp", .init = udp_recv_init}, -#define AFH_RECEIVER /* not active by default */ - -extern struct receiver receivers[]; -/** \endcond receiver */ - diff --git a/recv_common.c b/recv_common.c index 59630dfc..d48b3476 100644 --- a/recv_common.c +++ b/recv_common.c @@ -7,11 +7,14 @@ /** \file recv_common.c common functions of para_recv and para_audiod */ #include +#include +#include +#include "recv_cmd.lsg.h" #include "para.h" +#include "error.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "recv.h" #include "string.h" @@ -23,92 +26,93 @@ void recv_init(void) { int i; - FOR_EACH_RECEIVER(i) - receivers[i].init(&receivers[i]); -} - -static void *parse_receiver_args(int receiver_num, char *options) -{ - struct receiver *r = &receivers[receiver_num]; - char **argv; - int argc; - void *conf; - - if (options) { - argc = create_shifted_argv(options, " \t", &argv); - if (argc < 0) - return NULL; - } else { - argc = 1; - argv = para_malloc(2 * sizeof(char*)); - argv[1] = NULL; + FOR_EACH_RECEIVER(i) { + const struct lls_command *cmd = RECV_CMD(i); + const struct receiver *r = lls_user_data(cmd); + if (r && r->init) + r->init(); } - argv[0] = make_message("%s_recv", r->name); - conf = r->parse_config(argc, argv); - free_argv(argv); - return conf; } /** * Check if the given string is a valid receiver specifier. * - * \param \ra string of the form receiver_name:options - * \param receiver_num contains the number of the receiver upon success + * \param ra string of the form receiver_name [options...] + * \param lprp Filled in on success, undefined else. * * This function checks whether \a ra starts with the name of a receiver, * optionally followed by options for that receiver. If a valid receiver name * was found the remaining part of \a ra is passed to the receiver's config * parser. * - * \return On success, a pointer to the receiver-specific gengetopt args info - * struct is returned and \a receiver_num contains the number of the receiver. - * On errors, the function returns \p NULL. + * If a NULL pointer or an empty string is passed as the first argument, the + * hhtp receiver with no options is assumed. + * + * \return On success the number of the receiver is returned. On errors, the + * function calls exit(EXIT_FAILURE). */ -void *check_receiver_arg(char *ra, int *receiver_num) +int check_receiver_arg(const char *ra, struct lls_parse_result **lprp) { - int j; + int ret, argc, receiver_num; + char *errctx = NULL, **argv; + const struct lls_command *cmd; - PARA_DEBUG_LOG("checking %s\n", ra); - for (j = 0; receivers[j].name; j++) { - const char *name = receivers[j].name; - size_t len = strlen(name); - char c; - if (strlen(ra) < len) - continue; - if (strncmp(name, ra, len)) - continue; - c = ra[len]; - if (c && c != ' ') - continue; - if (c && !receivers[j].parse_config) - return NULL; - *receiver_num = j; - return parse_receiver_args(j, c? ra + len + 1: NULL); + *lprp = NULL; + if (!ra || !*ra) { + argc = 1; + argv = para_malloc(2 * sizeof(char*)); + argv[0] = para_strdup("http"); + argv[1] = NULL; + } else { + ret = create_argv(ra, " \t\n", &argv); + if (ret < 0) { + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + exit(EXIT_FAILURE); + } + argc = ret; } - PARA_ERROR_LOG("receiver not found\n"); - return NULL; + ret = lls(lls_lookup_subcmd(argv[0], recv_cmd_suite, &errctx)); + if (ret < 0) { + PARA_EMERG_LOG("%s: %s\n", errctx? errctx : argv[0], + para_strerror(-ret)); + exit(EXIT_FAILURE); + } + receiver_num = ret; + cmd = RECV_CMD(receiver_num); + ret = lls(lls_parse(argc, argv, cmd, lprp, &errctx)); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + exit(EXIT_FAILURE); + } + ret = receiver_num; + free_argv(argv); + return ret; } /** * Print out the help texts to all receivers. * - * \param flags Passed to \ref ggo_print_help(). + * \param detailed Whether to print the short or the detailed help. */ -void print_receiver_helps(unsigned flags) +void print_receiver_helps(bool detailed) { int i; - printf_or_die("\nAvailable receivers: "); - FOR_EACH_RECEIVER(i) - printf_or_die("%s%s", i? " " : "", receivers[i].name); - printf_or_die("\n"); + printf("\nAvailable receivers: "); + FOR_EACH_RECEIVER(i) { + const struct lls_command *cmd = RECV_CMD(i); + printf("%s%s", i? " " : "", lls_command_name(cmd)); + } + printf("\n\n"); FOR_EACH_RECEIVER(i) { - struct receiver *r = receivers + i; - if (!r->help.short_help) + const struct lls_command *cmd = RECV_CMD(i); + char *help = detailed? lls_long_help(cmd) : lls_short_help(cmd); + if (!help) continue; - printf_or_die("\n%s: %s", r->name, - r->help.purpose); - ggo_print_help(&r->help, flags); + printf("%s\n", help); + free(help); } } diff --git a/resample_filter.c b/resample_filter.c index 1699ed2c..72a1864e 100644 --- a/resample_filter.c +++ b/resample_filter.c @@ -8,20 +8,24 @@ #include #include +#include -#include "resample_filter.cmdline.h" +#include "filter_cmd.lsg.h" #include "para.h" #include "error.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "string.h" #include "check_wav.h" +#define U32_OPTVAL(_opt, _lpr) (FILTER_CMD_OPT_UINT32_VAL(RESAMPLE, _opt, _lpr)) +#define OPT_GIVEN(_opt, _lpr) (FILTER_CMD_OPT_GIVEN(RESAMPLE, _opt, _lpr)) + +/* effective values, may differ from config arg */ struct resample_context { - int channels; + uint32_t channels; int source_sample_rate; float ratio; SRC_STATE *src_state; @@ -32,10 +36,8 @@ static int resample_execute(struct btr_node *btrn, const char *cmd, char **resul { struct filter_node *fn = btr_context(btrn); struct resample_context *ctx = fn->private_data; - struct resample_filter_args_info *conf = fn->conf; - - return decoder_execute(cmd, conf->dest_sample_rate_arg, ctx->channels, - result); + uint32_t dsr = U32_OPTVAL(DEST_SAMPLE_RATE, fn->lpr); + return decoder_execute(cmd, dsr, ctx->channels, result); } static void resample_close(struct filter_node *fn) @@ -54,13 +56,12 @@ static void resample_close(struct filter_node *fn) static void resample_open(struct filter_node *fn) { struct resample_context *ctx = para_calloc(sizeof(*ctx)); - struct resample_filter_args_info *conf = fn->conf; struct btr_node *btrn = fn->btrn; struct wav_params wp; fn->private_data = ctx; fn->min_iqs = 2; - COPY_WAV_PARMS(&wp, conf); + LLS_COPY_WAV_PARMS(&wp, LSG_FILTER_CMD_RESAMPLE, fn->lpr); ctx->cwc = check_wav_init(btr_parent(btrn), btrn, &wp, NULL); btr_log_tree(btr_parent(btr_parent(btrn)), LL_INFO); } @@ -95,18 +96,17 @@ static int resample_set_params(struct filter_node *fn) { int ret; struct resample_context *ctx = fn->private_data; - struct resample_filter_args_info *conf = fn->conf; struct btr_node *btrn = fn->btrn; + struct lls_parse_result *lpr = fn->lpr; - ctx->channels = conf->channels_arg; - if (!conf->channels_given) { + ctx->channels = U32_OPTVAL(CHANNELS, lpr); + if (!OPT_GIVEN(CHANNELS, lpr)) { ret = get_btr_val("channels", btrn); if (ret >= 0) ctx->channels = ret; } - - ctx->source_sample_rate = conf->sample_rate_arg; - if (!conf->sample_rate_given) { + ctx->source_sample_rate = U32_OPTVAL(SAMPLE_RATE, lpr); + if (!OPT_GIVEN(SAMPLE_RATE, lpr)) { ret = get_btr_val("sample_rate", btrn); if (ret >= 0) ctx->source_sample_rate = ret; @@ -114,44 +114,37 @@ static int resample_set_params(struct filter_node *fn) /* reject all sample formats except 16 bit signed, little endian */ ret = get_btr_val("sample_format", btrn); if (ret >= 0 && ret != SF_S16_LE) { - const char *sample_formats[] = {SAMPLE_FORMATS}; + const char * const sample_formats[] = {SAMPLE_FORMATS}; PARA_ERROR_LOG("unsupported sample format: %s\n", sample_formats[ret]); return -ERRNO_TO_PARA_ERROR(EINVAL); } - ctx->ratio = (float)conf->dest_sample_rate_arg / ctx->source_sample_rate; + ctx->ratio = U32_OPTVAL(DEST_SAMPLE_RATE, lpr) + / (float)ctx->source_sample_rate; return 1; } static int resample_init(struct filter_node *fn) { - int ret, converter; + int ret; + const uint32_t trafo[] = { + [RCT_BEST] = SRC_SINC_BEST_QUALITY, + [RCT_MEDIUM] = SRC_SINC_MEDIUM_QUALITY, + [RCT_FASTEST] = SRC_SINC_FASTEST, + [RCT_ZERO_ORDER_HOLD] = SRC_ZERO_ORDER_HOLD, + [RCT_LINEAR] = SRC_LINEAR + }; struct resample_context *ctx = fn->private_data; - struct resample_filter_args_info *conf = fn->conf; + const struct lls_option *o_c = FILTER_CMD_OPT(RESAMPLE, CONVERTER); + uint32_t converter = U32_OPTVAL(CONVERTER, fn->lpr); + PARA_INFO_LOG("converter type: %s\n", + lls_enum_string_val(converter, o_c)); ret = resample_set_params(fn); if (ret < 0) return ret; - switch (conf->converter_arg) { - case converter_arg_best: - converter = SRC_SINC_BEST_QUALITY; - break; - case converter_arg_medium: - converter = SRC_SINC_MEDIUM_QUALITY; - break; - case converter_arg_fastest: - converter = SRC_SINC_FASTEST; - break; - case converter_arg_zero_order_hold: - converter = SRC_ZERO_ORDER_HOLD; - break; - case converter_arg_linear: - converter = SRC_LINEAR; - break; - default: - assert(0); - } - ctx->src_state = src_new(converter, conf->channels_arg, &ret); + ctx->src_state = src_new(trafo[converter], + U32_OPTVAL(CHANNELS, fn->lpr), &ret); if (!ctx->src_state) { PARA_ERROR_LOG("%s\n", src_strerror(ret)); return -E_LIBSAMPLERATE; @@ -201,7 +194,6 @@ static int resample_post_select(__a_unused struct sched *s, void *context) int ret; struct filter_node *fn = context; struct resample_context *ctx = fn->private_data; - struct resample_filter_args_info *conf = fn->conf; struct btr_node *btrn = fn->btrn; int16_t *in, *out; size_t in_bytes, num_frames; @@ -218,7 +210,7 @@ static int resample_post_select(__a_unused struct sched *s, void *context) if (ret <= 0) goto out; } - if (ctx->source_sample_rate == conf->dest_sample_rate_arg) { + if (ctx->source_sample_rate == U32_OPTVAL(DEST_SAMPLE_RATE, fn->lpr)) { /* * No resampling necessary. We do not splice ourselves out * though, since our children might want to ask us through the @@ -251,58 +243,45 @@ out: return ret; } -static int resample_parse_config(int argc, char **argv, void **config) +static void *resample_setup(const struct lls_parse_result *lpr) { - int ret, val, given; - struct resample_filter_args_info *conf = para_calloc(sizeof(*conf)); - - resample_filter_cmdline_parser(argc, argv, conf); + int given; + uint32_t u32; /* sanity checks */ - ret = -ERRNO_TO_PARA_ERROR(EINVAL); - val = conf->channels_arg; - given = conf->channels_given; - if (val < 0 || (val == 0 && given)) - goto err; - val = conf->sample_rate_arg; - given = conf->sample_rate_given; - if (val < 0 || (val == 0 && given)) - goto err; - val = conf->dest_sample_rate_arg; - given = conf->dest_sample_rate_given; - if (val < 0 || (val == 0 && given)) - goto err; - *config = conf; - return 1; -err: - free(conf); - return ret; + u32 = U32_OPTVAL(CHANNELS, lpr); + given = OPT_GIVEN(CHANNELS, lpr); + if (u32 == 0 && given) { + PARA_EMERG_LOG("fatal: zero channels?!\n"); + exit(EXIT_FAILURE); + } + u32 = U32_OPTVAL(SAMPLE_RATE, lpr); + given = OPT_GIVEN(SAMPLE_RATE, lpr); + if (u32 == 0 && given) { + PARA_EMERG_LOG("fatal: input sample rate can not be 0\n"); + exit(EXIT_FAILURE); + } + u32 = U32_OPTVAL(DEST_SAMPLE_RATE, lpr); + given = OPT_GIVEN(DEST_SAMPLE_RATE, lpr); + if (u32 == 0 && given) { + PARA_EMERG_LOG("fatal: destination sample rate can not be 0\n"); + exit(EXIT_FAILURE); + } + return NULL; } -static void resample_free_config(void *conf) +static void resample_teardown(__a_unused const struct lls_parse_result *lpr, + void *conf) { - if (!conf) - return; - resample_filter_cmdline_parser_free(conf); free(conf); } -/** - * The init function of the resample filter. - * - * \param f Structure to initialize. - */ -void resample_filter_init(struct filter *f) -{ - struct resample_filter_args_info dummy; - - resample_filter_cmdline_parser_init(&dummy); - f->close = resample_close; - f->open = resample_open; - f->pre_select = resample_pre_select; - f->post_select = resample_post_select; - f->parse_config = resample_parse_config; - f->free_config = resample_free_config; - f->execute = resample_execute; - f->help = (struct ggo_help)DEFINE_GGO_HELP(resample_filter); -} +const struct filter lsg_filter_cmd_com_resample_user_data = { + .setup = resample_setup, + .open = resample_open, + .pre_select = resample_pre_select, + .post_select = resample_post_select, + .close = resample_close, + .teardown = resample_teardown, + .execute = resample_execute +}; diff --git a/score.c b/score.c index 81b3ded0..ddd3c7a2 100644 --- a/score.c +++ b/score.c @@ -7,6 +7,7 @@ /** \file score.c Scoring functions to determine the audio file streaming order. */ #include #include +#include #include "para.h" #include "error.h" diff --git a/send.h b/send.h index 0c74f0ea..54205234 100644 --- a/send.h +++ b/send.h @@ -182,10 +182,10 @@ struct sender_status { void shutdown_client(struct sender_client *sc, struct sender_status *ss); void shutdown_clients(struct sender_status *ss); -void init_sender_status(struct sender_status *ss, char **access_arg, int num_access_args, - int port, int max_clients, int default_deny); +void init_sender_status(struct sender_status *ss, + const struct lls_opt_result *acl_opt_result, int port, + int max_clients, int default_deny); char *generic_sender_status(struct sender_status *ss, const char *name); - void generic_com_allow(struct sender_command_data *scd, struct sender_status *ss); void generic_com_deny(struct sender_command_data *scd, diff --git a/send_common.c b/send_common.c index ec7ab671..988e8d48 100644 --- a/send_common.c +++ b/send_common.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "para.h" #include "error.h" @@ -133,19 +134,33 @@ int send_queued_chunks(int fd, struct chunk_queue *cq) * Initialize a struct sender status. * * \param ss The struct to initialize. - * \param access_arg The array of access arguments given at the command line. - * \param num_access_args The number of elements in \a access_arg. + * \param acl_opt_result Contains array of --{http|dccp}-access arguments. * \param port The tcp or dccp port to listen on. * \param max_clients The maximal number of simultaneous connections. * \param default_deny Whether a blacklist should be used for access control. */ -void init_sender_status(struct sender_status *ss, char **access_arg, - int num_access_args, int port, int max_clients, int default_deny) +void init_sender_status(struct sender_status *ss, + const struct lls_opt_result *acl_opt_result, int port, + int max_clients, int default_deny) { + int i; + ss->listen_fd = -1; INIT_LIST_HEAD(&ss->client_list); ss->port = port; - acl_init(&ss->acl, access_arg, num_access_args); + + /* Initialize an access control list */ + INIT_LIST_HEAD(&ss->acl); + for (i = 0; i < lls_opt_given(acl_opt_result); i++) { + const char *arg = lls_string_val(i, acl_opt_result); + char addr[16]; + int mask; + if (!parse_cidr(arg, addr, sizeof(addr), &mask)) + PARA_WARNING_LOG("ACL syntax error: %s, ignoring\n", + arg); + else + acl_add_entry(&ss->acl, addr, mask); + } ss->num_clients = 0; ss->max_clients = max_clients; ss->default_deny = default_deny; diff --git a/server.c b/server.c index a023b152..43ede2a9 100644 --- a/server.c +++ b/server.c @@ -40,11 +40,12 @@ #include #include #include +#include +#include "server.lsg.h" #include "para.h" #include "error.h" #include "crypt.h" -#include "server.cmdline.h" #include "afh.h" #include "string.h" #include "afs.h" @@ -62,7 +63,6 @@ #include "signal.h" #include "user_list.h" #include "color.h" -#include "ggo.h" #include "version.h" /** Array of error strings. */ @@ -80,12 +80,15 @@ __printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log; struct misc_meta_data *mmd; /** - * The configuration of para_server + * The active value for all config options of para_server. * - * It also contains the options for the audio file selector, audio format - * handler and all supported senders. + * It is computed by merging the parse result of the command line options with + * the parse result of the config file. */ -struct server_args_info conf; +struct lls_parse_result *server_lpr = NULL; + +/* Command line options (no config file options). Used in handle_sighup(). */ +static struct lls_parse_result *cmdline_lpr; /** A random value used in child context for authentication. */ uint32_t afs_socket_cookie; @@ -93,9 +96,6 @@ uint32_t afs_socket_cookie; /** The mutex protecting the shared memory area containing the mmd struct. */ int mmd_mutex; -/** The file containing user information (public key, permissions). */ -static char *user_list_file = NULL; - static struct sched sched; static struct signal_task *signal_task; @@ -162,71 +162,97 @@ err_out: /** * (Re-)read the server configuration files. * - * \param override Passed to gengetopt to activate the override feature. + * \param reload Whether config file overrides command line. * - * This function also re-opens the logfile and sets the global \a - * user_list_file variable. + * This function also re-opens the logfile and the user list. On SIGHUP it is + * called from both server and afs context. */ -void parse_config_or_die(int override) +void parse_config_or_die(bool reload) { - char *home = para_homedir(); int ret; - char *cf; + char *cf = NULL, *errctx = NULL, *user_list_file = NULL; + void *map; + size_t sz; + int cf_argc; + char **cf_argv; + struct lls_parse_result *cf_lpr, *merged_lpr; + char *home = para_homedir(); daemon_close_log(); - if (conf.config_file_given) - cf = para_strdup(conf.config_file_arg); + if (OPT_GIVEN(CONFIG_FILE)) + cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE)); else cf = make_message("%s/.paraslash/server.conf", home); - free(user_list_file); - if (!conf.user_list_given) - user_list_file = make_message("%s/.paraslash/server.users", home); - else - user_list_file = para_strdup(conf.user_list_arg); - ret = file_exists(cf); - if (conf.config_file_given && !ret) { - ret = -1; - PARA_EMERG_LOG("can not read config file %s\n", cf); - goto out; + if (!mmd || getpid() != mmd->afs_pid) { + if (OPT_GIVEN(USER_LIST)) + user_list_file = para_strdup(OPT_STRING_VAL(USER_LIST)); + else + user_list_file = make_message("%s/.paraslash/server.users", home); } - if (ret) { - int tmp = conf.daemon_given; - struct server_cmdline_parser_params params = { - .override = override, - .initialize = 0, - .check_required = 1, - .check_ambiguity = 0, - .print_errors = !conf.daemon_given - }; - server_cmdline_parser_config_file(cf, &conf, ¶ms); - daemon_set_loglevel(conf.loglevel_arg); - conf.daemon_given = tmp; + free(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 free_cf; + if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE)) + goto free_cf; + ret = 0; + server_lpr = cmdline_lpr; + goto success; } - if (conf.logfile_given) { - daemon_set_logfile(conf.logfile_arg); + 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; + if (reload) /* config file overrides command line */ + ret = lls(lls_merge(cf_lpr, cmdline_lpr, CMD_PTR, &merged_lpr, + &errctx)); + else /* command line options overrride config file options */ + ret = lls(lls_merge(cmdline_lpr, cf_lpr, CMD_PTR, &merged_lpr, + &errctx)); + lls_free_parse_result(cf_lpr, CMD_PTR); + if (ret < 0) + goto free_cf; + if (server_lpr != cmdline_lpr) + lls_free_parse_result(server_lpr, CMD_PTR); + server_lpr = merged_lpr; +success: + daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL)); + if (OPT_GIVEN(LOGFILE)) { + daemon_set_logfile(OPT_STRING_VAL(LOGFILE)); daemon_open_log_or_die(); } - - if (daemon_init_colors_or_die(conf.color_arg, color_arg_auto, color_arg_no, - conf.logfile_given)) { + if (daemon_init_colors_or_die(OPT_UINT32_VAL(COLOR), COLOR_AUTO, + COLOR_NO, OPT_GIVEN(LOGFILE))) { int i; - for (i = 0; i < conf.log_color_given; i++) - daemon_set_log_color_or_die(conf.log_color_arg[i]); + for (i = 0; i < OPT_GIVEN(LOG_COLOR); i++) + daemon_set_log_color_or_die(lls_string_val(i, + OPT_RESULT(LOG_COLOR))); } daemon_set_flag(DF_LOG_PID); daemon_set_flag(DF_LOG_LL); daemon_set_flag(DF_LOG_TIME); - if (conf.log_timing_given) + if (OPT_GIVEN(LOG_TIMING)) daemon_set_flag(DF_LOG_TIMING); + daemon_set_priority(OPT_UINT32_VAL(PRIORITY)); + if (user_list_file) + init_user_list(user_list_file); ret = 1; -out: +free_cf: free(cf); - free(home); - if (ret > 0) - return; free(user_list_file); - user_list_file = NULL; - exit(EXIT_FAILURE); + if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + exit(EXIT_FAILURE); + } } /* @@ -234,9 +260,9 @@ out: */ static void handle_sighup(void) { + PARA_NOTICE_LOG("SIGHUP\n"); - parse_config_or_die(1); /* reopens log */ - init_user_list(user_list_file); /* reload user list */ + parse_config_or_die(true); if (mmd->afs_pid) kill(mmd->afs_pid, SIGHUP); } @@ -369,7 +395,8 @@ static int command_post_select(struct sched *s, void *context) */ for (i = sct->argc - 1; i >= 0; i--) memset(sct->argv[i], 0, strlen(sct->argv[i])); - sprintf(sct->argv[0], "para_server (serving %s)", peer_name); + i = sct->argc - 1 - lls_num_inputs(cmdline_lpr); + sprintf(sct->argv[i], "para_server (serving %s)", peer_name); handle_connect(new_fd, peer_name); /* never reached*/ out: @@ -387,7 +414,7 @@ static void init_server_command_task(int argc, char **argv) PARA_NOTICE_LOG("initializing tcp command socket\n"); sct->argc = argc; sct->argv = argv; - ret = para_listen_simple(IPPROTO_TCP, conf.port_arg); + ret = para_listen_simple(IPPROTO_TCP, OPT_UINT32_VAL(PORT)); if (ret < 0) goto err; sct->listen_fd = ret; @@ -426,7 +453,8 @@ static int init_afs(int argc, char **argv) for (i = argc - 1; i >= 0; i--) memset(argv[i], 0, strlen(argv[i])); - sprintf(argv[0], "para_server (afs)"); + i = argc - lls_num_inputs(cmdline_lpr) - 1; + sprintf(argv[i], "para_server (afs)"); close(afs_server_socket[0]); afs_init(afs_socket_cookie, afs_server_socket[1]); } @@ -445,45 +473,46 @@ static int init_afs(int argc, char **argv) return afs_server_socket[0]; } -__noreturn static void print_help_and_die(void) +static void handle_help_flags(void) { - struct ggo_help h = DEFINE_GGO_HELP(server); - bool d = conf.detailed_help_given; + char *help; + bool d = OPT_GIVEN(DETAILED_HELP); - ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS); - exit(0); + if (d) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP)) + help = lls_short_help(CMD_PTR); + else + return; + printf("%s\n", help); + free(help); + exit(EXIT_SUCCESS); } static void server_init(int argc, char **argv) { - struct server_cmdline_parser_params params = { - .override = 0, - .initialize = 1, - .check_required = 0, - .check_ambiguity = 0, - .print_errors = 1 - }; - int afs_socket, daemon_pipe = -1; + int ret, afs_socket, daemon_pipe = -1; + char *errctx; valid_fd_012(); - init_random_seed_or_die(); /* parse command line options */ - server_cmdline_parser_ext(argc, argv, &conf, ¶ms); - daemon_set_loglevel(conf.loglevel_arg); - version_handle_flag("server", conf.version_given); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); - daemon_set_priority(conf.priority_arg); - daemon_drop_privileges_or_die(conf.user_arg, conf.group_arg); - /* parse config file, open log and set defaults */ - parse_config_or_die(0); + ret = lls(lls_parse(argc, argv, CMD_PTR, &cmdline_lpr, &errctx)); + if (ret < 0) + goto fail; + server_lpr = cmdline_lpr; + daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL)); + daemon_drop_privileges_or_die(OPT_STRING_VAL(USER), + OPT_STRING_VAL(GROUP)); + version_handle_flag("server", OPT_GIVEN(VERSION)); + handle_help_flags(); + parse_config_or_die(false); + /* become daemon */ + if (OPT_GIVEN(DAEMON)) + daemon_pipe = daemonize(true /* parent waits for SIGTERM */); + init_random_seed_or_die(); daemon_log_welcome("server"); init_ipc_or_die(); /* init mmd struct and mmd->lock */ daemon_set_start_time(); - init_user_list(user_list_file); - /* become daemon */ - if (conf.daemon_given) - daemon_pipe = daemonize(true /* parent waits for us */); PARA_NOTICE_LOG("initializing audio format handlers\n"); afh_init(); @@ -491,16 +520,14 @@ static void server_init(int argc, char **argv) * Although afs uses its own signal handling we must ignore SIGUSR1 * _before_ the afs child process gets born by init_afs() below. It's * racy to do this in the child because the parent might send SIGUSR1 - * before the child gets a chance to ignore this signal -- only the - * good die young. - */ - para_sigaction(SIGUSR1, SIG_IGN); - /* - * We have to block SIGCHLD before the afs process is being forked off. - * Otherwise, para_server does not notice if afs dies before the + * before the child gets a chance to ignore this signal. + * + * We also have to block SIGCHLD before the afs process is created + * because otherwise para_server does not notice if afs dies before the * SIGCHLD handler has been installed for the parent process by * init_signal_task() below. */ + para_sigaction(SIGUSR1, SIG_IGN); para_block_signal(SIGCHLD); PARA_NOTICE_LOG("initializing the audio file selector\n"); afs_socket = init_afs(argc, argv); @@ -517,6 +544,13 @@ static void server_init(int argc, char **argv) close(daemon_pipe); } PARA_NOTICE_LOG("server init complete\n"); + return; +fail: + assert(ret < 0); + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + exit(EXIT_FAILURE); } static void status_refresh(void) @@ -572,9 +606,10 @@ int main(int argc, char *argv[]) mutex_lock(mmd_mutex); ret = schedule(&sched); sched_shutdown(&sched); - if (ret < 0) { + lls_free_parse_result(server_lpr, CMD_PTR); + if (server_lpr != cmdline_lpr) + lls_free_parse_result(cmdline_lpr, CMD_PTR); + if (ret < 0) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); - exit(EXIT_FAILURE); - } - exit(EXIT_SUCCESS); + exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); } diff --git a/server.cmd b/server.cmd deleted file mode 100644 index 5f46ba1e..00000000 --- a/server.cmd +++ /dev/null @@ -1,129 +0,0 @@ -BN: server -SF: command.c -SN: list of server commands ---- -N: ff -P: VSS_READ | VSS_WRITE -D: Jump N seconds forward or backward. -U: ff n[-] -H: This sets the 'R' (reposition request) bit of the vss status flags -H: which enqueues a request to jump n seconds forwards or backwards. -H: -H: Example: -H: -H: para_client ff 30- -H: -H: jumps 30 seconds backwards. ---- -N: help -P: 0 -D: Print online help. -U: help [command] -H: Without any arguments, help prints a list of available commands. When -H: called with a command name as first argument, it prints the description -H: of that command. ---- -N: hup -P: VSS_WRITE -D: Reload config file, log file and user list. -U: hup -H: Reread the config file and the user list file, close and reopen the log -H: file, and ask the afs process to do the same. Sending the HUP signal to -H: the server process has the same effect. ---- -N: jmp -P: VSS_READ | VSS_WRITE -D: Jump to the given position. -U: jmp n -H: Set the 'R' (reposition request) bit of the vss status flags and enqueue a -H: request to jump to n% of the current audio file, where 0 <= n <= 100. ---- -N: next -P: VSS_READ | VSS_WRITE -D: Close the current audio file. -U: next -H: Set the 'N' (next audio file) bit of the vss status flags which instructs the -H: server to close its current audio file if necessary. If the 'P' bit (playing) -H: is on, playing continues with the next audio file. This command is equivalent -H: to stop if paused, and has no effect if stopped. ---- -N: nomore -P: VSS_READ | VSS_WRITE -D: Stop playing after current audio file. -U: nomore -H: Set the 'O' (no more) bit of the vss status flags which asks para_server to -H: clear the 'P' (playing) bit after the 'N' (next audio file) bit transitions -H: from off to on (because the end of the current audio file is reached). Use this -H: command instead of stop if you don't like sudden endings. ---- -N: pause -P: VSS_READ | VSS_WRITE -D: Pause current audio file. -U: pause -H: Clear the 'P' (playing) bit of the vss status flags. ---- -N: play -P: VSS_READ | VSS_WRITE -D: Start or resume playing. -U: play -H: Set the 'P' (playing) bit of the vss status flags. ---- -N: sender -P: VSS_READ | VSS_WRITE -D: Control paraslash senders. -U: sender [s cmd [arguments]] -H: Send a command to a specific sender. The following commands are available, but -H: not all senders support every command. -H: -H: help, on, off, add, delete, allow, deny, status. -H: -H: The help command prints the help text of the given sender. If no command is -H: given the list of compiled in senders is shown. -H: -H: Example: -H: -H: para_client sender http help ---- -N: si -P: 0 -D: Print server info. -U: si -H: Show server and afs PID, number of connections, uptime and more. ---- -N: stat -P: VSS_READ -D: Print information about the current audio file. -U: stat [-n=num] [-p] -H: If -n is given, exit after the status information has been shown n times. -H: Otherwise, the command runs in an endless loop. -H: -H: The -p option activates parser-friendly output: Each status item is -H: prefixed with its size in bytes and the status item identifiers are -H: printed as numerical values. ---- -N: stop -P: VSS_READ | VSS_WRITE -D: Stop playing. -U: stop -H: Clear the 'P' (playing) bit and set the 'N' (next audio file) bit of the vss -H: status flags, effectively stopping playback. ---- -N: tasks -P: 0 -D: List server tasks. -U: tasks -H: For each task, print ID, status and name. ---- -N: term -P: VSS_READ | VSS_WRITE -D: Ask the server to terminate. -U: term -H: Shut down the server. Instead of this command, you can also send SIGINT or -H: SIGTERM to the para_server process. It should never be necessary to send -H: SIGKILL. ---- -N: version -P: 0 -D: Print the git version string of para_server. -U: version -H: Show version and other info. diff --git a/server.h b/server.h index 8de691ca..0bfca305 100644 --- a/server.h +++ b/server.h @@ -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. */ @@ -89,9 +87,37 @@ struct misc_meta_data { 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 --git a/spxdec_filter.c b/spxdec_filter.c index 644d287a..3266d090 100644 --- a/spxdec_filter.c +++ b/spxdec_filter.c @@ -50,7 +50,6 @@ #include "para.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "error.h" @@ -308,16 +307,10 @@ fail: return ret; } -/** - * The init function of the ogg/speex decoder. - * - * \param f Its fields are filled in by the function. - */ -void spxdec_filter_init(struct filter *f) -{ - f->open = spxdec_open; - f->close = speexdec_close; - f->pre_select = generic_filter_pre_select; - f->post_select = speexdec_post_select; - f->execute = speexdec_execute; -} +const struct filter lsg_filter_cmd_com_spxdec_user_data = { + .open = spxdec_open, + .close = speexdec_close, + .pre_select = generic_filter_pre_select, + .post_select = speexdec_post_select, + .execute = speexdec_execute, +}; diff --git a/sync_filter.c b/sync_filter.c index 82e86e90..efc10116 100644 --- a/sync_filter.c +++ b/sync_filter.c @@ -13,13 +13,13 @@ #include #include #include +#include +#include "filter_cmd.lsg.h" #include "para.h" -#include "sync_filter.cmdline.h" #include "list.h" #include "net.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "string.h" @@ -42,7 +42,7 @@ struct sync_buddy { struct list_head node; }; -/* Allocated in ->open() */ +/* Allocated in ->open(), stored in fn->private_data */ struct sync_filter_context { int listen_fd; struct list_head buddies; @@ -50,12 +50,6 @@ struct sync_filter_context { bool ping_sent; }; -/* Allocated and freed in ->parse_config() and ->free_config(). */ -struct sync_filter_config { - struct sync_filter_args_info *conf; - struct sync_buddy_info *buddy_info; -}; - #define FOR_EACH_BUDDY(_buddy, _list) \ list_for_each_entry(_buddy, _list, node) #define FOR_EACH_BUDDY_SAFE(_buddy, _tmp_buddy, _list) \ @@ -90,59 +84,59 @@ static void sync_close(struct filter_node *fn) fn->private_data = NULL; } -static void sync_free_config(void *conf) +static void sync_teardown(const struct lls_parse_result *lpr, void *conf) { - struct sync_filter_config *sfc = conf; - int i; + struct sync_buddy_info *sbi = conf; + int i, num_buddies = FILTER_CMD_OPT_GIVEN(SYNC, BUDDY, lpr); - for (i = 0; i < sfc->conf->buddy_given; i++) { - free(sfc->buddy_info[i].host); - freeaddrinfo(sfc->buddy_info[i].ai); + for (i = 0; i < num_buddies; i++) { + free(sbi[i].host); + freeaddrinfo(sbi[i].ai); } - sync_filter_cmdline_parser_free(sfc->conf); - free(sfc); + free(sbi); } static void sync_open(struct filter_node *fn) { int i, ret; - struct sync_filter_config *sfc = fn->conf; struct sync_buddy *buddy; struct sync_filter_context *ctx; - - assert(sfc); + struct sync_buddy_info *sbi = fn->conf; + uint32_t port = FILTER_CMD_OPT_UINT32_VAL(SYNC, PORT, fn->lpr); + unsigned buddy_given; + const struct lls_opt_result *r_b; ctx = fn->private_data = para_calloc(sizeof(*ctx)); INIT_LIST_HEAD(&ctx->buddies); - ctx->listen_fd = -1; /* create socket to listen for incoming packets */ ret = makesock( IPPROTO_UDP, true /* passive */, NULL /* no host required */, - sfc->conf->port_arg, + port, NULL /* no flowopts */ ); if (ret < 0) { - PARA_ERROR_LOG("could not create UDP listening socket %d\n", - sfc->conf->port_arg); + PARA_ERROR_LOG("could not create UDP listening socket %u\n", + port); return; } ctx->listen_fd = ret; PARA_INFO_LOG("listening on fd %d\n", ctx->listen_fd); - for (i = 0; i < sfc->conf->buddy_given; i++) { - struct sync_buddy_info *sbi = sfc->buddy_info + i; - const char *url = sfc->conf->buddy_arg[i]; + r_b = FILTER_CMD_OPT_RESULT(SYNC, BUDDY, fn->lpr); + buddy_given = lls_opt_given(r_b); + for (i = 0; i < buddy_given; i++) { int fd; + const char *url = lls_string_val(i, r_b); /* make buddy udp socket from address info */ assert(sbi->ai); ret = makesock_addrinfo( IPPROTO_UDP, false /* not passive */, - sbi->ai, + sbi[i].ai, NULL /* no flowopts */ ); if (ret < 0) { @@ -160,7 +154,7 @@ static void sync_open(struct filter_node *fn) } buddy = para_malloc(sizeof(*buddy)); buddy->fd = fd; - buddy->sbi = sbi; + buddy->sbi = sbi + i; buddy->ping_received = false; para_list_add(&buddy->node, &ctx->buddies); @@ -172,61 +166,50 @@ fail: } /* - * At parse config time, we build an array of struct sync_buddy_info with one - * entry for each buddy given in the arguments. This array is not affected by - * sync_close(), so information stored there can be used for multiple instances - * (para_audiod). We store the resolved url and the ->disabled bit in this - * array. + * Build an array of struct sync_buddy_info with one entry for each buddy given + * in the arguments. This array is not affected by sync_close(), so information + * stored there can be used for multiple instances (para_audiod). We store the + * resolved url and the ->disabled bit in this array. */ -static int sync_parse_config(int argc, char **argv, void **result) +static void *sync_setup(const struct lls_parse_result *lpr) { - int i, ret, n; - struct sync_filter_config *sfc; - struct sync_filter_args_info *conf = para_malloc(sizeof(*conf)); - - sync_filter_cmdline_parser(argc, argv, conf); /* exits on error */ - sfc = para_calloc(sizeof(*sfc)); - sfc->conf = conf; - n = conf->buddy_given; - sfc->buddy_info = para_malloc((n + 1) * sizeof(*sfc->buddy_info)); - PARA_INFO_LOG("initializing buddy info array of length %d\n", n); + int i, ret; + unsigned n; + struct sync_buddy_info *sbi; + const struct lls_opt_result *r_b; + + r_b = FILTER_CMD_OPT_RESULT(SYNC, BUDDY, lpr); + n = lls_opt_given(r_b); + sbi = para_malloc(n * sizeof(*sbi)); + PARA_INFO_LOG("initializing buddy info array of length %u\n", n); for (i = 0; i < n; i++) { - const char *url = conf->buddy_arg[i]; + const char *url = lls_string_val(i, r_b); size_t len = strlen(url); char *host = para_malloc(len + 1); int port; struct addrinfo *ai; - struct sync_buddy_info *sbi = sfc->buddy_info + i; if (!parse_url(url, host, len, &port)) { - free(host); PARA_ERROR_LOG("could not parse url %s\n", url); - ret = -ERRNO_TO_PARA_ERROR(EINVAL); - goto fail; + exit(EXIT_FAILURE); } if (port < 0) - port = conf->port_arg; + port = FILTER_CMD_OPT_UINT32_VAL(SYNC, PORT, lpr); ret = lookup_address(IPPROTO_UDP, false /* not passive */, host, port, &ai); if (ret < 0) { - PARA_ERROR_LOG("host lookup failure for %s\n", url); - free(host); - goto fail; + PARA_ERROR_LOG("host lookup failure for %s: %s\n", + url, para_strerror(-ret)); + exit(EXIT_FAILURE); } - sbi->url = url; - sbi->host = host; - sbi->port = port; - sbi->ai = ai; - sbi->disabled = false; + sbi[i].url = url; + sbi[i].host = host; + sbi[i].port = port; + sbi[i].ai = ai; + sbi[i].disabled = false; PARA_DEBUG_LOG("buddy #%d: %s\n", i, url); } - *result = sfc; - return 1; -fail: - assert(ret < 0); - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - sync_free_config(sfc); - return ret; + return sbi; } /* @@ -260,11 +243,12 @@ static void sync_disable_active_buddies(struct sync_filter_context *ctx) } static void sync_set_timeout(struct sync_filter_context *ctx, - struct sync_filter_config *sfc) + struct lls_parse_result *lpr) { + uint32_t ms = FILTER_CMD_OPT_UINT32_VAL(SYNC, TIMEOUT, lpr); struct timeval to; - ms2tv(sfc->conf->timeout_arg, &to); + ms2tv(ms, &to); tv_add(now, &to, &ctx->timeout); } @@ -273,7 +257,6 @@ static void sync_pre_select(struct sched *s, void *context) int ret; struct filter_node *fn = context; struct sync_filter_context *ctx = fn->private_data; - struct sync_filter_config *sfc = fn->conf; if (list_empty(&ctx->buddies)) return sched_min_delay(s); @@ -286,7 +269,7 @@ static void sync_pre_select(struct sched *s, void *context) if (ret == 0) return; if (ctx->timeout.tv_sec == 0) { /* must ping buddies */ - sync_set_timeout(ctx, sfc); + sync_set_timeout(ctx, fn->lpr); return sched_min_delay(s); } if (sync_complete(ctx)) /* push down what we have */ @@ -310,7 +293,6 @@ static int sync_post_select(__a_unused struct sched *s, void *context) int ret; struct filter_node *fn = context; struct sync_filter_context *ctx = fn->private_data; - struct sync_filter_config *sfc = fn->conf; struct sync_buddy *buddy, *tmp; if (list_empty(&ctx->buddies)) @@ -324,7 +306,7 @@ static int sync_post_select(__a_unused struct sched *s, void *context) if (ret == 0) return 0; if (ctx->timeout.tv_sec == 0) - sync_set_timeout(ctx, sfc); + sync_set_timeout(ctx, fn->lpr); else { if (tv_diff(&ctx->timeout, now, NULL) < 0) { sync_disable_active_buddies(ctx); @@ -395,21 +377,11 @@ out: return ret; } -/** - * The synchronization filter. - * - * \param f Pointer to the struct to initialize. - */ -void sync_filter_init(struct filter *f) -{ - struct sync_filter_args_info dummy; - - sync_filter_cmdline_parser_init(&dummy); - f->open = sync_open; - f->close = sync_close; - f->pre_select = sync_pre_select; - f->post_select = sync_post_select; - f->parse_config = sync_parse_config; - f->free_config = sync_free_config; - f->help = (struct ggo_help)DEFINE_GGO_HELP(sync_filter); -} +const struct filter lsg_filter_cmd_com_sync_user_data = { + .setup = sync_setup, + .open = sync_open, + .pre_select = sync_pre_select, + .post_select = sync_post_select, + .close = sync_close, + .teardown = sync_teardown +}; diff --git a/t/makefile.test b/t/makefile.test index 15bb6859..a71963c9 100644 --- a/t/makefile.test +++ b/t/makefile.test @@ -18,6 +18,7 @@ endif tests := $(sort $(wildcard $(test_dir)/t[0-9][0-9][0-9][0-9]-*.sh)) +check: $(tests) test: $(tests) $(tests): all @@ -30,4 +31,4 @@ test-clean: $(RM) -r $(results_dir) $(RM) -r $(trash_dir) -.PHONY: $(tests) test-help +.PHONY: $(tests) test-help test-clean test check diff --git a/t/t0001-oggdec-correctness.sh b/t/t0001-oggdec-correctness.sh index 554dfdf8..0b30539a 100755 --- a/t/t0001-oggdec-correctness.sh +++ b/t/t0001-oggdec-correctness.sh @@ -11,7 +11,7 @@ implementation.' test_require_objects "oggdec_filter" missing_objects="$result" -test_require_executables oggdec sha1sum +test_require_executables oggdec shasum missing_executables="$result" get_audio_file_paths ogg @@ -28,9 +28,9 @@ for ogg in $oggs; do continue fi test_expect_success "${ogg##*/}" " - $PARA_FILTER -f oggdec < $ogg | sha1sum > filter.sha1 && - oggdec --quiet --raw --output - - < $ogg | sha1sum > oggdec.sha1 && - diff -u filter.sha1 oggdec.sha1 + $PARA_FILTER -f oggdec < $ogg | shasum > filter.sha && + oggdec --quiet --raw --output - - < $ogg | shasum > oggdec.sha && + diff -u filter.sha oggdec.sha " done test_done diff --git a/t/t0004-server.sh b/t/t0004-server.sh index 1963748f..5329ef0f 100755 --- a/t/t0004-server.sh +++ b/t/t0004-server.sh @@ -28,7 +28,7 @@ declare -a commands=() cmdline=() required_objects=() good=() bad=() i=0 commands[$i]="help" cmdline[$i]="help" -good[$i]='help server ----' +good[$i]='help ----' let i++ commands[$i]="init" diff --git a/t/t0005-man.sh b/t/t0005-man.sh index fa32d411..ee1949c0 100755 --- a/t/t0005-man.sh +++ b/t/t0005-man.sh @@ -13,8 +13,6 @@ filter/receiver/writer options as appropriate ' . ${0%/*}/test-lib.sh -rfw_regex='Options for .\{100,\}Options for ' # recv/filter/writer - grep_man() { local regex="$1" exe="$2" @@ -24,20 +22,19 @@ grep_man() # check that options of all reveivers/filters/writers are contained # in the man pages -regex="$rfw_regex" -test_expect_success 'para_recv: receiver options' "grep_man '$regex' recv" -test_expect_success 'para_filter: filter options' "grep_man '$regex' filter" -test_expect_success 'para_write: writer options' "grep_man '$regex' write" +test_expect_success 'para_recv: receiver options' "grep_man 'RECEIVERS' recv" +test_expect_success 'para_filter: filter options' "grep_man 'FILTERS' filter" +test_expect_success 'para_write: writer options' "grep_man 'WRITERS' write" test_require_objects "audiod" if [[ -n "$result" ]]; then test_skip 'para_audiod' "missing object(s): $result" else test_expect_success 'para_audiod: receivers' \ - "grep_man 'Options for the http receiver' audiod" + "grep_man 'RECEIVERS' audiod" test_expect_success 'para_audiod: filters' \ - "grep_man 'Options for the compress filter' audiod" + "grep_man 'FILTERS' audiod" test_expect_success 'para_audiod: writers' \ - "grep_man 'Options for the file writer' audiod" + "grep_man 'WRITERS' audiod" fi # check various command lists @@ -56,7 +53,7 @@ missing_objects="$result" if [[ -n "$missing_objects" ]]; then test_skip "para_server" "missing object(s): $missing_objects" else - regex='LIST OF SERVER COMMANDS.\{100,\}LIST OF AFS COMMANDS' + regex='LIST OF SERVER COMMANDS.\{100,\}' test_expect_success 'para_server: server/afs commands' \ "grep_man '$regex' server" fi diff --git a/t/test-lib.sh b/t/test-lib.sh index 1f9913e3..9c6516e2 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -311,7 +311,7 @@ fi fixup_dirs [[ -z "$o_executables" ]] && o_executables="para_afh para_audioc para_audiod - para_client para_fade para_filter para_gui para_recv para_server + para_client para_mixer para_filter para_gui para_recv para_server para_write" for exe in $o_executables; do export $(tr 'a-z' 'A-Z' <<< $exe)="$o_executables_dir/$exe" diff --git a/time.c b/time.c index 6bb9a50f..90c67356 100644 --- a/time.c +++ b/time.c @@ -198,22 +198,14 @@ void compute_chunk_time(long unsigned chunk_num, struct timeval *clock_get_realtime(struct timeval *tv) { static struct timeval user_friendly; + struct timespec t; + int ret; if (!tv) tv = &user_friendly; -#ifdef HAVE_CLOCK_GETTIME - { - struct timespec t; - int ret; - - ret = clock_gettime(CLOCK_REALTIME, &t); - assert(ret == 0); - tv->tv_sec = t.tv_sec; - tv->tv_usec = t.tv_nsec / 1000; - } -#else - #include - gettimeofday(tv, NULL); -#endif /* HAVE_CLOCK_GETTIME */ + ret = clock_gettime(CLOCK_REALTIME, &t); + assert(ret == 0); + tv->tv_sec = t.tv_sec; + tv->tv_usec = t.tv_nsec / 1000; return tv; } diff --git a/udp_recv.c b/udp_recv.c index b803b497..a5dfc879 100644 --- a/udp_recv.c +++ b/udp_recv.c @@ -13,16 +13,16 @@ #include #include #include +#include +#include "recv_cmd.lsg.h" #include "para.h" #include "error.h" #include "portable_io.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "recv.h" -#include "udp_recv.cmdline.h" #include "string.h" #include "net.h" #include "fd.h" @@ -103,13 +103,6 @@ static void udp_recv_close(struct receiver_node *rn) btr_pool_free(rn->btrp); } -static void *udp_recv_parse_config(int argc, char **argv) -{ - struct udp_recv_args_info *tmp = para_calloc(sizeof(*tmp)); - udp_recv_cmdline_parser(argc, argv, tmp); - return tmp; -} - /* * Perform AF-independent joining of multicast receive addresses. * @@ -173,58 +166,33 @@ err: static int udp_recv_open(struct receiver_node *rn) { - struct udp_recv_args_info *c = rn->conf; - char *iface = c->iface_given ? c->iface_arg : NULL; + struct lls_parse_result *lpr = rn->lpr; + const char *iface = RECV_CMD_OPT_STRING_VAL(UDP, IFACE, lpr); + const char *host = RECV_CMD_OPT_STRING_VAL(UDP, HOST, lpr); + uint32_t port = RECV_CMD_OPT_UINT32_VAL(UDP, PORT, lpr); int ret; - ret = makesock(IPPROTO_UDP, 1, c->host_arg, c->port_arg, NULL); + ret = makesock(IPPROTO_UDP, 1, host, port, NULL); if (ret < 0) - goto err; + return ret; rn->fd = ret; - ret = mcast_receiver_setup(rn->fd, iface); - if (ret < 0) { - close(rn->fd); + if (ret < 0) goto err; - } - ret = mark_fd_nonblocking(rn->fd); - if (ret < 0) { - close(rn->fd); + if (ret < 0) goto err; - } - PARA_INFO_LOG("receiving from %s:%d, fd=%d\n", c->host_arg, - c->port_arg, rn->fd); + PARA_INFO_LOG("receiving from %s:%u, fd=%d\n", host, port, rn->fd); rn->btrp = btr_pool_new("udp_recv", 320 * 1024); return rn->fd; err: + close(rn->fd); return ret; } -static void udp_recv_free_config(void *conf) -{ - udp_recv_cmdline_parser_free(conf); - free(conf); -} - -/** - * The init function of the udp receiver. - * - * \param r Pointer to the receiver struct to initialize. - * - * Initialize all function pointers of \a r. - */ -void udp_recv_init(struct receiver *r) -{ - struct udp_recv_args_info dummy; - - udp_recv_cmdline_parser_init(&dummy); - r->open = udp_recv_open; - r->close = udp_recv_close; - r->pre_select = udp_recv_pre_select; - r->post_select = udp_recv_post_select; - r->parse_config = udp_recv_parse_config; - r->free_config = udp_recv_free_config; - r->help = (struct ggo_help)DEFINE_GGO_HELP(udp_recv); - udp_recv_cmdline_parser_free(&dummy); -} +const struct receiver lsg_recv_cmd_com_udp_user_data = { + .open = udp_recv_open, + .close = udp_recv_close, + .pre_select = udp_recv_pre_select, + .post_select = udp_recv_post_select, +}; diff --git a/udp_send.c b/udp_send.c index 425118a1..8c9eebc9 100644 --- a/udp_send.c +++ b/udp_send.c @@ -15,8 +15,9 @@ #include #include #include +#include -#include "server.cmdline.h" +#include "server.lsg.h" #include "para.h" #include "error.h" #include "string.h" @@ -94,11 +95,11 @@ static int mcast_sender_setup(struct sender_client *sc) { struct sockaddr_storage ss; socklen_t sslen = sizeof(ss); - int ttl = conf.udp_ttl_arg, id = 0; + int ttl = OPT_INT32_VAL(UDP_TTL), id = 0; const int on = 1; - if (conf.udp_mcast_iface_given) { - char *iface = conf.udp_mcast_iface_arg; + if (OPT_GIVEN(UDP_MCAST_IFACE)) { + const char *iface = OPT_STRING_VAL(UDP_MCAST_IFACE); id = if_nametoindex(iface); if (id == 0) @@ -174,7 +175,7 @@ static int udp_resolve_target(const char *url, struct sender_command_data *scd) ret = parse_fec_url(url, scd); if (ret) return ret; - port = scd->port > 0 ? scd->port : conf.udp_default_port_arg; + port = scd->port > 0 ? scd->port : OPT_UINT32_VAL(UDP_DEFAULT_PORT); ret = para_connect_simple(IPPROTO_UDP, scd->host, port); if (ret < 0) @@ -372,7 +373,7 @@ static char *udp_status(void) "port: %s\n" "targets: %s\n", (sender_status == SENDER_on)? "on" : "off", - stringify_port(conf.udp_default_port_arg, "udp"), + stringify_port(OPT_UINT32_VAL(UDP_DEFAULT_PORT), "udp"), tgts? tgts : "(none)" ); free(tgts); @@ -385,10 +386,11 @@ static void udp_init_target_list(void) int i; INIT_LIST_HEAD(&targets); - for (i = 0; i < conf.udp_target_given; i++) { - if (udp_resolve_target(conf.udp_target_arg[i], &scd) < 0) + for (i = 0; i < OPT_GIVEN(UDP_TARGET); i++) { + const char *arg = lls_string_val(i, OPT_RESULT(UDP_TARGET)); + if (udp_resolve_target(arg, &scd) < 0) PARA_CRIT_LOG("not adding requested target '%s'\n", - conf.udp_target_arg[i]); + arg); else udp_com_add(&scd); } @@ -438,7 +440,7 @@ void udp_send_init(struct sender *s) s->client_cmds[SENDER_delete] = udp_com_delete; sender_status = SENDER_off; udp_init_target_list(); - if (!conf.udp_no_autostart_given) + if (!OPT_GIVEN(UDP_NO_AUTOSTART)) sender_status = SENDER_on; PARA_DEBUG_LOG("udp sender init complete\n"); } diff --git a/user_list.c b/user_list.c index 9c751244..23fe086f 100644 --- a/user_list.c +++ b/user_list.c @@ -48,7 +48,7 @@ static void populate_user_list(char *user_list_file) if (strcmp(w, "user")) continue; PARA_DEBUG_LOG("found entry for user %s\n", n); - ret = get_asymmetric_key(k, LOAD_PUBLIC_KEY, &pubkey); + ret = get_public_key(k, &pubkey); if (ret < 0) { PARA_NOTICE_LOG("skipping entry for user %s: %s\n", n, para_strerror(-ret)); @@ -63,7 +63,7 @@ static void populate_user_list(char *user_list_file) if (ret <= CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) { PARA_WARNING_LOG("public key %s too short (%d)\n", k, ret); - free_asymmetric_key(pubkey); + free_public_key(pubkey); continue; } u = para_malloc(sizeof(*u)); @@ -114,7 +114,7 @@ void init_user_list(char *user_list_file) list_for_each_entry_safe(u, tmp, &user_list, node) { list_del(&u->node); free(u->name); - free_asymmetric_key(u->pubkey); + free_public_key(u->pubkey); free(u); } } else diff --git a/user_list.h b/user_list.h index 3a77e98f..948ed503 100644 --- a/user_list.h +++ b/user_list.h @@ -15,6 +15,7 @@ * is read at server startup. */ enum server_command_permissions { + NO_PERMISSION_REQUIRED = 0, /** None of the below. */ AFS_READ = 1, /** Read-only operation on the AFS database. */ AFS_WRITE = 2, /** Read-write operation on the AFS database. */ VSS_READ = 4, /** Read-only operation on the virtual streaming system. */ diff --git a/vss.c b/vss.c index 5484db9d..3632cf54 100644 --- a/vss.c +++ b/vss.c @@ -19,7 +19,9 @@ #include #include #include +#include +#include "server.lsg.h" #include "para.h" #include "error.h" #include "portable_io.h" @@ -29,7 +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,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. */ @@ -96,6 +99,8 @@ struct vss_task { size_t header_len; /** Time between audio file headers are sent. */ struct timeval header_interval; + /* Only used if afh supports dynamic chunks. */ + void *afh_context; }; /** @@ -347,6 +352,8 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst) 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 @@ -356,12 +363,20 @@ static void vss_get_chunk(int chunk_num, struct vss_task *vsst, * rather than the unmodified header (chunk zero). */ if (chunk_num == 0 && vsst->header_len > 0) { + assert(vsst->header_buf); *buf = vsst->header_buf; /* stripped header */ *sz = vsst->header_len; return; } - 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, @@ -846,7 +861,7 @@ static void vss_eof(struct vss_task *vsst) set_eof_barrier(vsst); afh_free_header(vsst->header_buf, mmd->afd.audio_format_id); vsst->header_buf = NULL; - para_munmap(vsst->map, mmd->size); + para_munmap(vsst->map, vsst->mapsize); vsst->map = NULL; mmd->chunks_sent = 0; //mmd->offset = 0; @@ -855,7 +870,9 @@ static void vss_eof(struct vss_task *vsst) mmd->afd.afhi.chunk_tv.tv_usec = 0; free(mmd->afd.afhi.chunk_table); mmd->afd.afhi.chunk_table = NULL; - mmd->size = 0; + vsst->mapsize = 0; + afh_close(vsst->afh_context, mmd->afd.audio_format_id); + vsst->afh_context = NULL; mmd->events++; } @@ -973,11 +990,11 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds) ret = -ERRNO_TO_PARA_ERROR(errno); goto err; } - mmd->size = statbuf.st_size; - ret = para_mmap(mmd->size, PROT_READ, MAP_PRIVATE | MAP_POPULATE, + 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; @@ -986,7 +1003,7 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds) mmd->num_played++; mmd->new_vss_status_flags &= (~VSS_NEXT); afh_get_header(&mmd->afd.afhi, mmd->afd.audio_format_id, - vsst->map, mmd->size, &vsst->header_buf, &vsst->header_len); + vsst->map, vsst->mapsize, &vsst->header_buf, &vsst->header_len); return; err: free(mmd->afd.afhi.chunk_table); @@ -1076,7 +1093,7 @@ static void vss_send(struct vss_task *vsst) */ 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; } @@ -1113,7 +1130,7 @@ static int vss_post_select(struct sched *s, void *context) set_eof_barrier(vsst); mmd->chunks_sent = 0; mmd->current_chunk = afh_get_start_chunk(mmd->repos_request, - &mmd->afd.afhi); + &mmd->afd.afhi, mmd->afd.audio_format_id); mmd->new_vss_status_flags &= ~VSS_REPOS; set_mmd_offset(); } @@ -1166,10 +1183,8 @@ void init_vss_task(int afs_socket, struct sched *s) static struct vss_task vss_task_struct, *vsst = &vss_task_struct; int i; char *hn = para_hostname(), *home = para_homedir(); - long unsigned announce_time = conf.announce_time_arg > 0? - conf.announce_time_arg : 300, - autoplay_delay = conf.autoplay_delay_arg > 0? - conf.autoplay_delay_arg : 0; + long unsigned announce_time = OPT_UINT32_VAL(ANNOUNCE_TIME), + autoplay_delay = OPT_UINT32_VAL(AUTOPLAY_DELAY); vsst->header_interval.tv_sec = 5; /* should this be configurable? */ vsst->afs_socket = afs_socket; ms2tv(announce_time, &vsst->announce_tv); @@ -1182,7 +1197,7 @@ void init_vss_task(int afs_socket, struct sched *s) 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 --git a/wav_filter.c b/wav_filter.c index 88047adb..eb38cd7d 100644 --- a/wav_filter.c +++ b/wav_filter.c @@ -12,7 +12,6 @@ #include "error.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "filter.h" #include "string.h" @@ -120,15 +119,9 @@ err: return ret; } -/** - * The init function of the wav filter. - * - * \param f Structure to initialize. - */ -void wav_filter_init(struct filter *f) -{ - f->close = wav_close; - f->open = wav_open; - f->pre_select = wav_pre_select; - f->post_select = wav_post_select; -} +const struct filter lsg_filter_cmd_com_wav_user_data = { + .close = wav_close, + .open = wav_open, + .pre_select = wav_pre_select, + .post_select = wav_post_select, +}; diff --git a/web/.htaccess b/web/.htaccess index 425c2190..2b7fe221 100644 --- a/web/.htaccess +++ b/web/.htaccess @@ -6,7 +6,7 @@ AddIcon ../signature.png *.asc AddDescription "Digital signature" *.asc - AddIcon ../tar-icon.png *.tgz *.tar.bz2 - AddDescription "current master snapshot" -git.tar.bz2 .g*.tar.bz2 .g*.dirty.tar.bz2 - AddDescription "release tarball" *.tgz *.tar.bz2 + AddIcon ../tar-icon.png *.tgz *.tar.bz2 *.tar.xz + AddDescription "current master snapshot" -git.tar.xz .g*.tar.xz .g*.dirty.tar.xz + AddDescription "release tarball" *.tgz *.tar.bz2 *.tar.xz diff --git a/web/about.in.html b/web/about.in.html index 2c72f281..d91a2ad4 100644 --- a/web/about.in.html +++ b/web/about.in.html @@ -5,7 +5,7 @@ Paraslash is a collection of network audio streaming tools for Unix systems. It is written in C and released under the GPLv2.
    -
  • Runs on Linux, Mac OS, FreeBSD, NetBSD
  • +
  • Runs on Linux, FreeBSD, NetBSD
  • Mp3, ogg/vorbis, ogg/speex, aac (m4a), wma, flac and ogg/opus support
  • http, dccp and udp network streaming
  • Stand-alone decoder, player, tagger
  • diff --git a/web/documentation.in.html b/web/documentation.in.html index b226943b..9e8b59ce 100644 --- a/web/documentation.in.html +++ b/web/documentation.in.html @@ -29,7 +29,7 @@ [para_filter] [para_write] [para_gui] - [para_fade] + [para_mixere] [para_play]

    diff --git a/web/download.in.html b/web/download.in.html index a09fd1c8..1ca8a4d2 100644 --- a/web/download.in.html +++ b/web/download.in.html @@ -9,9 +9,9 @@ provided at this point. There are several ways to download the source: Clone the git repository by executing -

     
    +		
     
     			git clone git://git.tuebingen.mpg.de/paraslash.git
    -		 

    +

    The repository contains the full history of the project since 2006, all work in progress and the source @@ -52,7 +52,7 @@ provided at this point. There are several ways to download the source: Whenever significant changes are incorporated a - tarball + tarball of the current master branch is created. All changes in this tarball will be included in the next release. Like diff --git a/web/images/paraslash.ico b/web/images/paraslash.ico index fd17a649..06ff0336 100644 Binary files a/web/images/paraslash.ico and b/web/images/paraslash.ico differ diff --git a/web/images/paraslash.png b/web/images/paraslash.png index a06d6a1a..804752cf 100644 Binary files a/web/images/paraslash.png and b/web/images/paraslash.png differ diff --git a/web/manual.md b/web/manual.md index 12454ee2..5216c6b5 100644 --- a/web/manual.md +++ b/web/manual.md @@ -152,9 +152,8 @@ an array of offsets within the audio file. ### 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 ### @@ -167,7 +166,7 @@ window. Appearance can be customized via themes. para_gui provides key-bindings for the most common server commands and new key-bindings can be added easily. -### para_fade ### +### para_mixer ### An alarm clock and volume-fader for OSS and ALSA. @@ -187,7 +186,7 @@ Requirements 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 \ @@ -203,6 +202,13 @@ code repository, execute 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. @@ -214,13 +220,6 @@ disto. On BSD systems the gnu make executable is often called gmake. during compilation require the _Bourne again shell_. It is most likely already installed. -- [gengetopt](ftp://ftp.gnu.org/pub/gnu/gengetopt/) is needed to -generate the C code for the command line parsers of all paraslash -executables. - -- [help2man](ftp://ftp.gnu.org/pub/gnu/help2man) is used to create -the man pages. - - [m4](ftp://ftp.gnu.org/pub/gnu/m4/). Some source files are generated from templates by the m4 macro processor. @@ -249,8 +248,11 @@ recognized. The mp3 tagger also needs this library for modifying 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. @@ -448,9 +450,9 @@ User management para_server uses a challenge-response mechanism to authenticate requests from incoming connections, similar to ssh's public key authentication method. Authenticated connections are encrypted using -a stream cipher, either RC4 or AES in integer counter mode. +the AES stream cipher in integer counter mode. -In this chapter we briefly describe RSA, RC4 and AES, and sketch the +In this chapter we briefly describe RSA and AES, and sketch the [authentication handshake](#Client-server.authentication) between para_client and para_server. User management is discussed in the section on [the user_list file](#The.user_list.file). @@ -458,33 +460,33 @@ These sections are all about communication between the client and the server. Connecting para_audiod is a different matter and is described in a [separate section](#Connecting.para_audiod). -RSA, RC4, AES -------------- +RSA and AES +----------- -RSA is an asymmetric block cipher which is used in many applications, -including ssh and gpg. An RSA key consists in fact of two keys, +A block cipher is a transformation which operates on fixed-length +blocks. For symmetric block ciphers the transformation is determined +by a single key for both encryption and decryption. For asymmetric +block ciphers, on the other hand, the key consists of two parts, called the public key and the private key. A message can be encrypted -with either key and only the counterpart of that key can decrypt -the message. While RSA can be used for both signing and encrypting -a message, paraslash uses RSA only for the latter purpose. The -RSA public key encryption and signatures algorithms are defined in -detail in RFC 2437. - -RC4 is a stream cipher, i.e. the input is XORed with a pseudo-random -key stream to produce the output. Decryption uses the same function -calls as encryption. While RC4 supports variable key lengths, -paraslash uses a fixed length of 256 bits, which is considered a -strong encryption by today's standards. Since the same key must never -be used twice, a different, randomly-generated key is used for every -new connection. +with either key and only the counterpart of that key can decrypt the +message. Asymmetric block ciphers can be used for both signing and +encrypting a message. + +RSA is an asymmetric block cipher which is used in many applications, +including ssh and gpg. The RSA public key encryption and signatures +algorithms are defined in detail in RFC 2437. Paraslash relies on +RSA for authentication. + +Stream ciphers XOR the input with a pseudo-random key stream to produce +the output. Decryption uses the same function calls as encryption. +Any block cipher can be turned into a stream cipher by generating the +pseudo-random key stream by encrypting successive values of a counter +(counter mode). AES, the advanced encryption standard, is a well-known symmetric block -cipher, i.e. a transformation operating on fixed-length blocks which -is determined by a single key for both encryption and decryption. Any -block cipher can be turned into a stream cipher by generating -a pseudo-random key stream by encrypting successive values of a -counter. The AES_CTR128 stream cipher used in paraslash is obtained -in this way from the AES block cipher with a 128 bit block size. +cipher. Paraslash employs AES in counter mode as described above to +encrypt communications. Since a stream cipher key must not be used +twice, a random key is generated for every new connection. Client-server authentication ---------------------------- @@ -524,8 +526,8 @@ point on the communication is encrypted using the stream cipher with the session key known to both peers. paraslash relies on the quality of the pseudo-random bytes provided -by the crypto library (openssl or libgcrypt), on the security of the -implementation of the RSA, RC4 and AES crypto routines and on the +by the crypto library (openssl or libgcrypt), on the security of +the implementation of the RSA and AES crypto routines and on the infeasibility to invert the SHA1 function. Neither para_server or para_client create RSA keys on their @@ -1700,10 +1702,6 @@ emulation for backwards compatibility. This API is rather simple but also limited. For example only one application can open the device at any time. The OSS writer is activated by default on BSD Systems. -- *OSX*. Mac OS X has yet another API called CoreAudio. The OSX writer -for this API is only compiled in on such systems and is of course -the default there. - - *FILE*. The file writer allows to capture the audio stream and write the PCM data to a file on the file system rather than playing it through a sound device. It is supported on all platforms and is diff --git a/wma_afh.c b/wma_afh.c index b4935f0d..ebb2f9c3 100644 --- a/wma_afh.c +++ b/wma_afh.c @@ -38,8 +38,7 @@ static int count_frames(const char *buf, int buf_size, uint32_t packet_size, sfc++; } PARA_INFO_LOG("%d frames, %d superframes\n", fc, sfc); - if (num_superframes) - *num_superframes = sfc; + *num_superframes = sfc; return fc; } @@ -68,7 +67,7 @@ static int put_utf8(uint32_t val, char *result) *out++ = in; return 1; } - bytes = (wma_log2(in) + 4) / 5; + bytes = DIV_ROUND_UP(wma_log2(in), 5); shift = (bytes - 1) * 6; *out++ = (256 - (256 >> bytes)) | (in >> shift); while (shift >= 6) { @@ -229,6 +228,7 @@ static int wma_make_chunk_table(char *buf, size_t buf_size, uint32_t packet_size } } afhi->chunks_total = j; + set_max_chunk_size(afhi); set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv); return 1; fail: @@ -365,8 +365,7 @@ static int make_cdo(struct taginfo *tags, const struct asf_object *cdo, struct asf_object *result) { const char *cr, *rating; /* orig data */ - uint16_t orig_title_bytes, orig_artist_bytes, orig_cr_bytes, - orig_comment_bytes, orig_rating_bytes; + uint16_t orig_cr_bytes, orig_rating_bytes; /* pointers to new UTF-16 tags */ char *artist = NULL, *title = NULL, *comment = NULL; /* number of bytes in UTF-16 for the new tags */ @@ -389,6 +388,7 @@ static int make_cdo(struct taginfo *tags, const struct asf_object *cdo, comment_bytes = ret; if (cdo) { + uint16_t orig_title_bytes, orig_artist_bytes, orig_comment_bytes; /* * Sizes of the five fields (stored as 16-bit numbers) are * located after the header (16 bytes) and the cdo size (8 @@ -402,10 +402,7 @@ static int make_cdo(struct taginfo *tags, const struct asf_object *cdo, cr = cdo->ptr + 34 + orig_title_bytes + orig_artist_bytes; rating = cr + orig_cr_bytes + orig_comment_bytes; } else { - orig_title_bytes = 2; - orig_artist_bytes = 2; orig_cr_bytes = 2; - orig_comment_bytes = 2; orig_rating_bytes = 2; cr = null; rating = null; diff --git a/wma_common.c b/wma_common.c index 6d57c00b..fcaf3ce4 100644 --- a/wma_common.c +++ b/wma_common.c @@ -178,7 +178,5 @@ __a_const int wma_log2(unsigned int v) v >>= 8; n += 8; } - n += log2_tab[v]; - - return n; + return n + log2_tab[v]; } diff --git a/wmadec_filter.c b/wmadec_filter.c index 4c7c047a..525ed315 100644 --- a/wmadec_filter.c +++ b/wmadec_filter.c @@ -24,11 +24,11 @@ #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" @@ -1259,16 +1259,10 @@ static void wmadec_open(struct filter_node *fn) fn->min_iqs = 4096; } -/** - * The init function of the wma decoder. - * - * \param f Its fields are filled in by the function. - */ -void wmadec_filter_init(struct filter *f) -{ - f->open = wmadec_open; - f->close = wmadec_close; - f->execute = wmadec_execute; - f->pre_select = generic_filter_pre_select; - f->post_select = wmadec_post_select; -} +const struct filter lsg_filter_cmd_com_wmadec_user_data = { + .open = wmadec_open, + .close = wmadec_close, + .execute = wmadec_execute, + .pre_select = generic_filter_pre_select, + .post_select = wmadec_post_select, +}; diff --git a/write.c b/write.c index 62caf097..a1907b6f 100644 --- a/write.c +++ b/write.c @@ -8,17 +8,17 @@ #include #include +#include +#include "write_cmd.lsg.h" +#include "write.lsg.h" #include "para.h" #include "string.h" -#include "write.cmdline.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "stdin.h" #include "buffer_tree.h" #include "write.h" -#include "write_common.h" #include "fd.h" #include "error.h" #include "version.h" @@ -27,48 +27,30 @@ /** Array of error strings. */ DEFINE_PARA_ERRLIST; -static struct write_args_info conf; +#define CMD_PTR (lls_cmd(0, write_suite)) +#define OPT_RESULT(_name, _lpr) \ + (lls_opt_result(LSG_WRITE_PARA_WRITE_OPT_ ## _name, _lpr)) +#define OPT_GIVEN(_name, _lpr) (lls_opt_given(OPT_RESULT(_name, _lpr))) +#define OPT_UINT32_VAL(_name, _lpr) (lls_uint32_val(0, OPT_RESULT(_name, _lpr))) static struct stdin_task sit; - static int loglevel; INIT_STDERR_LOGGING(loglevel) -__noreturn static void print_help_and_die(void) +static void handle_help_flag(struct lls_parse_result *lpr) { - struct ggo_help h = DEFINE_GGO_HELP(write); - bool d = conf.detailed_help_given; - - ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS); - print_writer_helps(d? GPH_MODULE_FLAGS_DETAILED : GPH_MODULE_FLAGS); - exit(0); -} - -/* - * Parse config and register a task for a writer node. - * - * \param arg Command line arguments. - * \param parent The new node will be a child of \a parent. - * \param wn The writer node. - * - * If arg is \p NULL, the OS-dependent default writer is used with no - * arguments. The default writers are alsa for Linux, osx for OS X, oss for - * *BSD, and the file writer if the default writer is not supported. - * - * Once the writer configuration has been retrieved from the ->parse_config - * callback a writer node is created, its buffer tree node is added to the - * buffer tree as a child of the given parent. - * - * Finally, the new writer node's task structure is initialized and registered - * to the paraslash scheduler. - * - * \return Standard. - */ -static void setup_writer_node(const char *arg, struct btr_node *parent, - struct writer_node *wn, struct sched *s) -{ - wn->conf = check_writer_arg_or_die(arg, &wn->writer_num); - register_writer_node(wn, parent, s); + char *help; + + if (OPT_GIVEN(DETAILED_HELP, lpr)) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP, lpr)) + help = lls_short_help(CMD_PTR); + else + return; + printf("%s\n", help); + free(help); + print_writer_helps(OPT_GIVEN(DETAILED_HELP, lpr)); + exit(EXIT_SUCCESS); } struct write_task { @@ -88,9 +70,9 @@ static int write_post_select(__a_unused struct sched *s, void *context) return check_wav_post_select(wt->cwc); } -static int setup_and_schedule(void) +static int setup_and_schedule(struct lls_parse_result *lpr) { - int i, ret; + int i, n, ret, writer_given = OPT_GIVEN(WRITER, lpr); struct btr_node *cw_btrn; struct writer_node *wns; static struct sched s; @@ -101,7 +83,7 @@ static int setup_and_schedule(void) EMBRACE(.name = "stdin")); stdin_task_register(&sit, &s); - COPY_WAV_PARMS(&wp, &conf); + LLS_COPY_WAV_PARMS(&wp, LSG_WRITE_PARA_WRITE, lpr); wt.cwc = check_wav_init(sit.btrn, NULL, &wp, &cw_btrn); wt.task = task_register(&(struct task_info) { .name = "write", @@ -109,28 +91,26 @@ static int setup_and_schedule(void) .post_select = write_post_select, .context = &wt, }, &s); - if (!conf.writer_given) { - wns = para_calloc(sizeof(*wns)); - setup_writer_node(NULL, cw_btrn, wns, &s); - i = 1; - } else { - wns = para_calloc(conf.writer_given * sizeof(*wns)); - for (i = 0; i < conf.writer_given; i++) - setup_writer_node(conf.writer_arg[i], cw_btrn, - wns + i, &s); - } + n = writer_given? writer_given : 1; + wns = para_calloc(n * sizeof(*wns)); + for (i = 0; i < n; i++) { + const char *arg = i < writer_given? + lls_string_val(i, OPT_RESULT(WRITER, lpr)) : NULL; + wns[i].wid = check_writer_arg_or_die(arg, &wns[i].lpr); + register_writer_node(wns + i, cw_btrn, &s); + } s.default_timeout.tv_sec = 10; s.default_timeout.tv_usec = 50000; ret = schedule(&s); if (ret >= 0) { int j, ts; - for (j = 0; j < i; j++) { + for (j = 0; j < n; j++) { struct writer_node *wn = wns + j; ts = task_status(wn->task); assert(ts < 0); if (ts != -E_WRITE_COMMON_EOF && ts != -E_BTR_EOF) { - const char *name = writer_names[wn->writer_num]; + const char *name = writer_name(wn->wid); PARA_ERROR_LOG("%s: %s\n", name, para_strerror(-ts)); if (ret >= 0) @@ -138,14 +118,12 @@ static int setup_and_schedule(void) } } } - for (i--; i >= 0; i--) { + for (i = n - 1; i >= 0; i--) { struct writer_node *wn = wns + i; - struct writer *w = writers + wn->writer_num; - - w->close(wn); + writer_get(wn->wid)->close(wn); btr_remove_node(&wn->btrn); - w->free_config(wn->conf); - free(wn->conf); + lls_free_parse_result(wns[i].lpr, + lls_cmd(wn->wid, write_cmd_suite)); } free(wns); check_wav_shutdown(wt.cwc); @@ -167,18 +145,23 @@ static int setup_and_schedule(void) int main(int argc, char *argv[]) { int ret; - - write_cmdline_parser(argc, argv, &conf); - loglevel = get_loglevel_by_name(conf.loglevel_arg); - writer_init(); - version_handle_flag("write", conf.version_given); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); - - ret = setup_and_schedule(); + struct lls_parse_result *lpr; + char *errctx; + + ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx)); + if (ret < 0) + goto out; + loglevel = OPT_UINT32_VAL(LOGLEVEL, lpr); + version_handle_flag("write", OPT_GIVEN(VERSION, lpr)); + handle_help_flag(lpr); + ret = setup_and_schedule(lpr); + lls_free_parse_result(lpr, CMD_PTR); +out: if (ret < 0) { + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - exit(EXIT_FAILURE); } - exit(EXIT_SUCCESS); + return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/write.h b/write.h index c7c227ee..fd3983dd 100644 --- a/write.h +++ b/write.h @@ -6,19 +6,16 @@ /** \file write.h Writer-related structures. */ -/** The list of supported writers. */ -enum writer_enum {WRITER_ENUM}; - /** * Describes one running instance of a writer. */ struct writer_node { - /** The number of this writer. */ - int writer_num; + /** The ID of this writer. */ + int wid; /** Writer-specific data. */ void *private_data; - /** The writer-specific configuration of this node. */ - void *conf; + /** The parsed command line, merged with options given in the config file. */ + struct lls_parse_result *lpr; /** The buffer tree node associated with this writer node. */ struct btr_node *btrn; /** The task of this writer node. */ @@ -29,30 +26,6 @@ struct writer_node { /** Describes one supported writer. */ struct writer { - /** - * The init function of the writer. - * - * It must fill in all other function pointers of the given - * writer structure. - */ - void (*init)(struct writer *w); - /** - * The command line parser of the writer. - * - * It should check whether the command line options given by \a argv - * and \a argc are valid and return a pointer to the writer-specific - * configuration data determined by these options. This function must - * either succeed or call exit(). Note that parse_config_or_die() might - * be called more than once with different values of \a options. \sa - * \ref free_config(). - */ - void *(*parse_config_or_die)(int argc, char **argv); - /** - * Deallocate all configuration resources. - * - * This should free whatever was allocated by \ref parse_config_or_die(). - */ - void (*free_config)(void *config); /** * Prepare the fd sets for select. * @@ -72,8 +45,6 @@ struct writer { * This function is assumed to succeed. */ void (*close)(struct writer_node *); - /** The short and the log help text of this writer. */ - struct ggo_help help; /** * The callback handler. * @@ -83,14 +54,23 @@ struct writer { btr_command_handler execute; }; -/** Loop over each supported writer. */ -#define FOR_EACH_WRITER(i) for (i = 0; i < NUM_SUPPORTED_WRITERS; i++) - -/** Declare the init functions of all supported writers. */ -DECLARE_WRITER_INITS; +#define WRITE_CMD(_num) (lls_cmd(_num, write_cmd_suite)) -/** Array containing the name of each writer. */ -extern const char *writer_names[]; +#define WRITE_CMD_OPT_RESULT(_cmd, _opt, _lpr) \ + (lls_opt_result(LSG_WRITE_CMD_ ## _cmd ## _OPT_ ## _opt, _lpr)) +#define WRITE_CMD_OPT_GIVEN(_cmd, _opt, _lpr) \ + (lls_opt_given(WRITE_CMD_OPT_RESULT(_cmd, _opt, _lpr))) +#define WRITE_CMD_OPT_UINT32_VAL(_cmd, _opt, _lpr) \ + (lls_uint32_val(0, WRITE_CMD_OPT_RESULT(_cmd, _opt, (_lpr)))) +#define WRITE_CMD_OPT_STRING_VAL(_cmd, _opt, _lpr) \ + (lls_string_val(0, WRITE_CMD_OPT_RESULT(_cmd, _opt, (_lpr)))) -/** The writer structure for each supported writer. */ -extern struct writer writers[NUM_SUPPORTED_WRITERS]; +int check_writer_arg_or_die(const char *wa, struct lls_parse_result **lprp); +const struct writer *writer_get(int wid); +const char *writer_name(int wid); +void register_writer_node(struct writer_node *wn, struct btr_node *parent, + struct sched *s); +void get_btr_sample_rate(struct btr_node *btrn, int32_t *result); +void get_btr_channels(struct btr_node *btrn, int32_t *result); +void get_btr_sample_format(struct btr_node *btrn, int32_t *result); +void print_writer_helps(bool detailed); diff --git a/write_common.c b/write_common.c index cdb67e58..d4d1b10b 100644 --- a/write_common.c +++ b/write_common.c @@ -7,90 +7,123 @@ /** \file write_common.c common functions of para_audiod and para_write */ #include +#include +#include "write_cmd.lsg.h" #include "para.h" #include "string.h" #include "list.h" #include "sched.h" -#include "ggo.h" #include "buffer_tree.h" #include "write.h" #include "error.h" -#include "write_common.h" -/** the array containing the names of all supported writers */ -const char *writer_names[] ={WRITER_NAMES}; +/** Loop over all writers. */ +#define FOR_EACH_WRITER(i) for (i = 1; lls_cmd(i, write_cmd_suite); i++) -/** the array of supported writers */ -struct writer writers[NUM_SUPPORTED_WRITERS] = {WRITER_ARRAY}; -/** - * Call the init function of each supported paraslash writer. - */ -void writer_init(void) +static inline bool writer_supported(int wid) +{ + return lls_user_data(WRITE_CMD(wid)); +} + +/* simply return the first available writer */ +static int default_writer_id(void) { int i; FOR_EACH_WRITER(i) - writers[i].init(&writers[i]); + if (writer_supported(i)) + return i; + assert(0); /* the file writer should always be available */ } + /** - * Check if given string is a valid command line for any writer. + * Return the writer structure from a writer ID. * - * \param \wa String of the form writer_name:options. - * \param writer_num Contains the number of the writer upon success. + * \param wid If non-positive, a pointer to the default writer is returned. * - * This function checks whether \a wa starts with the name of a supported - * paraslash writer, optionally followed by a colon and any options for that - * writer. If a valid writer name was found and further are present, the - * remaining part of \a wa is passed to that writer's config parser. + * \return Pointer to a (constant) struct writer. + */ +const struct writer *writer_get(int wid) +{ + if (wid < 0) + wid = default_writer_id(); + return lls_user_data(WRITE_CMD(wid)); +} + +/** + * Return name of the writer identified by a writer ID. + * + * \param wid If non-positive, the name of the default writer is returned. * - * \return On success, a pointer to the gengetopt args info struct is returned - * and \a writer_num contains the number of the writer. Otherwise this function - * prints an error message and calls exit(). + * \return The returned buffer must not be freed by the caller. */ -void *check_writer_arg_or_die(const char *wa, int *writer_num) +const char *writer_name(int wid) { - int i, ret, argc; - const char *cmdline; - char **argv; - void *conf; + if (wid <= 0) + wid = default_writer_id(); + return lls_command_name(WRITE_CMD(wid)); +} - if (!wa || !*wa) { - i = DEFAULT_WRITER; - cmdline = NULL; - goto check; - } - PARA_INFO_LOG("checking %s\n", wa); - FOR_EACH_WRITER(i) { - const char *name = writer_names[i]; - size_t len = strlen(name); - char c; +/** + * Check if the given string is a valid command line for any writer. + * + * \param wa String of the form writer_name options. + * \param lprp Contains the parsed command line on success. + * + * If wa is \p NULL, the (configuration-dependent) default writer is assumed. + * Otherwise, the function checks whether \a wa starts with the name of a + * supported writer. If a valid writer name was found, the rest of the command + * line is passed to the config parser of this writer. + * + * \return On success, the positive writer ID is returned. Otherwise the + * function prints an error message and calls exit(). + */ +int check_writer_arg_or_die(const char *wa, struct lls_parse_result **lprp) +{ + int ret, writer_num, argc; + char **argv = NULL, *errctx = NULL; + const struct lls_command *cmd; - if (strlen(wa) < len) - continue; - if (strncmp(name, wa, len)) - continue; - c = wa[len]; - if (!c || c == ' ') { - cmdline = c? wa + len + 1 : NULL; - goto check; - } - } - PARA_EMERG_LOG("invalid writer %s\n", wa); - exit(EXIT_FAILURE); -check: - ret = create_shifted_argv(cmdline, " \t", &argv); - if (ret < 0) { - PARA_EMERG_LOG("%s: %s\n", wa, para_strerror(-ret)); - exit(EXIT_FAILURE); + if (!wa || !*wa) { + writer_num = default_writer_id(); + cmd = WRITE_CMD(writer_num); + argv = para_malloc(2 * sizeof(char *)); + argc = 1; + argv[0] = para_strdup(lls_command_name(cmd)); + argv[1] = NULL; + goto parse; } + ret = create_argv(wa, " \t\n", &argv); + if (ret < 0) + goto fail; argc = ret; - argv[0] = make_message("%s_write", writer_names[i]); - *writer_num = i; - conf = writers[i].parse_config_or_die(argc, argv); + ret = lls(lls_lookup_subcmd(argv[0], write_cmd_suite, &errctx)); + if (ret < 0) + goto free_argv; + writer_num = ret; + cmd = WRITE_CMD(writer_num); + if (!writer_supported(writer_num)) { + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + errctx = make_message("%s writer is not supported", + lls_command_name(cmd)); + goto free_argv; + } +parse: + ret = lls(lls_parse(argc, argv, cmd, lprp, &errctx)); + if (ret >= 0) + ret = writer_num; +free_argv: free_argv(argv); - return conf; + if (ret >= 0) + return ret; +fail: + if (errctx) + PARA_ERROR_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + exit(EXIT_FAILURE); } /** @@ -99,20 +132,17 @@ check: * \param wn The writer node to open. * \param parent The parent btr node (the source for the writer node). * \param s The scheduler instance to register the task to. - * - * The configuration of the writer node stored in \p wn->conf must be - * initialized before this function may be called. */ void register_writer_node(struct writer_node *wn, struct btr_node *parent, struct sched *s) { - struct writer *w = writers + wn->writer_num; + const struct writer *w = writer_get(wn->wid); wn->btrn = btr_new_node(&(struct btr_node_description) - EMBRACE(.name = writer_names[wn->writer_num], .parent = parent, + EMBRACE(.name = writer_name(wn->wid), .parent = parent, .handler = w->execute, .context = wn)); wn->task = task_register(&(struct task_info) { - .name = writer_names[wn->writer_num], + .name = writer_name(wn->wid), .pre_select = w->pre_select, .post_select = w->post_select, .context = wn, @@ -122,24 +152,29 @@ void register_writer_node(struct writer_node *wn, struct btr_node *parent, /** * Print the help text of all writers to stdout. * - * \param flags Passed to \ref ggo_print_help(). + * \param detailed Whether to print the short or the detailed help. */ -void print_writer_helps(unsigned flags) +void print_writer_helps(bool detailed) { int i; - printf_or_die("\nAvailable writers: "); - FOR_EACH_WRITER(i) - printf_or_die("%s%s", i? " " : "", writer_names[i]); - printf_or_die("\n"); + printf("\nAvailable writers: "); FOR_EACH_WRITER(i) { - struct writer *w = writers + i; - - if (!w->help.short_help) + if (!writer_supported(i)) + continue; + printf("%s%s", i? " " : "", writer_name(i)); + } + printf("\n"); + FOR_EACH_WRITER(i) { + const struct lls_command *cmd = WRITE_CMD(i); + char *help; + if (!writer_supported(i)) + continue; + help = detailed? lls_long_help(cmd) : lls_short_help(cmd); + if (!help) continue; - printf_or_die("\n%s: %s", writer_names[i], - w->help.purpose); - ggo_print_help(&w->help, flags); + printf("%s\n", help); + free(help); } } diff --git a/write_common.h b/write_common.h deleted file mode 100644 index 18288753..00000000 --- a/write_common.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) 2006 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file write_common.h Exported symbols from write_common.c. */ - -void writer_init(void); -void *check_writer_arg_or_die(const char *wa, int *writer_num); -void print_writer_helps(unsigned flags); -void register_writer_node(struct writer_node *wn, struct btr_node *parent, - struct sched *s); -void get_btr_sample_rate(struct btr_node *btrn, int32_t *result); -void get_btr_channels(struct btr_node *btrn, int32_t *result); -void get_btr_sample_format(struct btr_node *btrn, int32_t *result);