From e35ba8684be9951fa2129503477ccd5ed6e4e5fc Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Mon, 11 Dec 2017 23:13:37 +0100 Subject: Simplify, do not install examples or info manual, and change license MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 16 +- LICENSE | 31 +- Makefile | 350 ++----- README | 3 - arg.h | 38 + bus-broadcast.1 | 26 + bus-chgrp.1 | 29 + bus-chmod.1 | 40 + bus-chown.1 | 32 + bus-create.1 | 28 + bus-listen.1 | 23 + bus-remove.1 | 20 + bus-wait.1 | 24 + bus.1 | 69 ++ bus.5 | 42 + bus.c | 385 ++++++++ bus.h | 325 +++++++ bus.texinfo | 2079 ++++++++++++++++++++++++++++++++++++++++++ bus_chmod.3 | 56 ++ bus_chown.3 | 43 + bus_close.3 | 28 + bus_create.3 | 55 ++ bus_open.3 | 59 ++ bus_poll.3 | 79 ++ bus_read.3 | 71 ++ bus_unlink.3 | 48 + bus_write.3 | 56 ++ config.mk | 6 + dist/arch/stable/.gitignore | 6 - dist/arch/stable/PKGBUILD | 27 - dist/arch/stable/bus.install | 20 - doc/info/bus.texinfo | 2079 ------------------------------------------ doc/info/fdl.texinfo | 505 ---------- doc/man/bus-broadcast.1 | 35 - doc/man/bus-chgrp.1 | 38 - doc/man/bus-chmod.1 | 49 - doc/man/bus-chown.1 | 41 - doc/man/bus-create.1 | 37 - doc/man/bus-listen.1 | 32 - doc/man/bus-remove.1 | 29 - doc/man/bus-wait.1 | 33 - doc/man/bus.1 | 77 -- doc/man/bus.5 | 50 - doc/man/bus_chmod.3 | 64 -- doc/man/bus_chown.3 | 51 -- doc/man/bus_close.3 | 36 - doc/man/bus_create.3 | 63 -- doc/man/bus_open.3 | 67 -- doc/man/bus_poll.3 | 87 -- doc/man/bus_read.3 | 79 -- doc/man/bus_unlink.3 | 56 -- doc/man/bus_write.3 | 64 -- doc/man/libbus.7 | 39 - doc/protocol | 1 - fdl.texinfo | 505 ++++++++++ libbus.7 | 31 + libbus.c | 1173 ++++++++++++++++++++++++ src/bus.c | 1197 ------------------------ src/bus.h | 347 ------- src/cmdline.c | 429 --------- 60 files changed, 5475 insertions(+), 5933 deletions(-) create mode 100644 arg.h create mode 100644 bus-broadcast.1 create mode 100644 bus-chgrp.1 create mode 100644 bus-chmod.1 create mode 100644 bus-chown.1 create mode 100644 bus-create.1 create mode 100644 bus-listen.1 create mode 100644 bus-remove.1 create mode 100644 bus-wait.1 create mode 100644 bus.1 create mode 100644 bus.5 create mode 100644 bus.c create mode 100644 bus.h create mode 100644 bus.texinfo create mode 100644 bus_chmod.3 create mode 100644 bus_chown.3 create mode 100644 bus_close.3 create mode 100644 bus_create.3 create mode 100644 bus_open.3 create mode 100644 bus_poll.3 create mode 100644 bus_read.3 create mode 100644 bus_unlink.3 create mode 100644 bus_write.3 create mode 100644 config.mk delete mode 100644 dist/arch/stable/.gitignore delete mode 100644 dist/arch/stable/PKGBUILD delete mode 100644 dist/arch/stable/bus.install delete mode 100644 doc/info/bus.texinfo delete mode 100644 doc/info/fdl.texinfo delete mode 100644 doc/man/bus-broadcast.1 delete mode 100644 doc/man/bus-chgrp.1 delete mode 100644 doc/man/bus-chmod.1 delete mode 100644 doc/man/bus-chown.1 delete mode 100644 doc/man/bus-create.1 delete mode 100644 doc/man/bus-listen.1 delete mode 100644 doc/man/bus-remove.1 delete mode 100644 doc/man/bus-wait.1 delete mode 100644 doc/man/bus.1 delete mode 100644 doc/man/bus.5 delete mode 100644 doc/man/bus_chmod.3 delete mode 100644 doc/man/bus_chown.3 delete mode 100644 doc/man/bus_close.3 delete mode 100644 doc/man/bus_create.3 delete mode 100644 doc/man/bus_open.3 delete mode 100644 doc/man/bus_poll.3 delete mode 100644 doc/man/bus_read.3 delete mode 100644 doc/man/bus_unlink.3 delete mode 100644 doc/man/bus_write.3 delete mode 100644 doc/man/libbus.7 create mode 100644 fdl.texinfo create mode 100644 libbus.7 create mode 100644 libbus.c delete mode 100644 src/bus.c delete mode 100644 src/bus.h delete mode 100644 src/cmdline.c diff --git a/.gitignore b/.gitignore index 8350fba..ce4b9c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,14 @@ -_/ -obj/ -bin/ -\#*\# -.* -!.git* +*\#* *~ *.o *.out *.su *.gch - +*.so +*.a +*.lo +*.log +*.toc +*.aux +*.pdf +/bus diff --git a/LICENSE b/LICENSE index b253b5b..53d4a24 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,15 @@ -MIT/X Consortium License +ISC License -Copyright © 2015 Mattias Andrée +© 2015, 2017 Mattias Andrée -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile index e75a5a7..5ee0e25 100644 --- a/Makefile +++ b/Makefile @@ -1,272 +1,90 @@ -# bus - simple message broadcasting IPC system -# See LICENSE file for copyright and license details. - -PREFIX = /usr -BIN = /bin -BINDIR = ${PREFIX}${BIN} -LIB = /lib -LIBDIR = ${PREFIX}${LIB} -INCLUDE = /include -INCLUDEDIR = ${PREFIX}${INCLUDE} -DATA = /share -DATADIR = ${PREFIX}${DATA} -LICENSEDIR = ${DATADIR}/licenses -MANDIR = ${DATADIR}/man -INFODIR = $(DATADIR)/info -DOCDIR = $(DATADIR)/doc - -PKGNAME = bus - - -MAN1 = bus bus-broadcast bus-create bus-listen bus-remove bus-wait bus-chmod bus-chown bus-chgrp -MAN3 = bus_create bus_unlink bus_open bus_close bus_read bus_write bus_poll bus_chmod bus_chown -MAN5 = bus -MAN7 = libbus - -EXAMPLES = audio-volume-control daemon-dependencies nonblocking telephony-and-music timed -EXAMPLE_audio-volume-control = amixer cleanup init monitor README -EXAMPLE_daemon-dependencies = announce.c await-ready.c await-started.c cleanup.c d-network d-ntp \ - d-ssh init.c Makefile README require.c start-daemon.c test-daemon.c -EXAMPLE_nonblocking = cleanup.c init.c Makefile poll.c README write.c -EXAMPLE_telephony-and-music = cleanup.c end-call.c init.c Makefile monitor.c README receive-or-make-call.c -EXAMPLE_timed = cleanup.c init.c Makefile poll.c read.c README slow-poll.c write.c - - -FLAGS = -std=c99 -Wall -Wextra -pedantic -O2 - -LIB_MAJOR = 3 -LIB_MINOR = 1 -LIB_VERSION = ${LIB_MAJOR}.${LIB_MINOR} -VERSION = 3.1.6 - - - -default: bus man info -all: bus doc -doc: man info pdf dvi ps -man: man1 man3 man5 man7 -bus: bin lib -bin: bin/bus -lib: so a -so: bin/libbus.so.${LIB_VERSION} bin/libbus.so.${LIB_MAJOR} bin/libbus.so -a: bin/libbus.a -man1: $(foreach M,${MAN1},bin/${M}.1) -man3: $(foreach M,${MAN3},bin/${M}.3) -man5: $(foreach M,${MAN5},bin/${M}.5) -man7: $(foreach M,${MAN7},bin/${M}.7) -info: bin/bus.info -pdf: bin/bus.pdf -dvi: bin/bus.dvi -ps: bin/bus.ps - -bin/%.1: doc/man/%.1 - @echo SED $@ - @mkdir -p bin - @sed 's/%VERSION%/${VERSION}/g' < $< > $@ - -bin/%.3: doc/man/%.3 - @echo SED $@ - @mkdir -p bin - @sed 's/%VERSION%/${VERSION}/g' < $< > $@ - -bin/%.5: doc/man/%.5 - @echo SED $@ - @mkdir -p bin - @sed 's/%VERSION%/${VERSION}/g' < $< > $@ - -bin/%.7: doc/man/%.7 - @echo SED $@ - @mkdir -p bin - @sed 's/%VERSION%/${VERSION}/g' < $< > $@ - -bin/libbus.a: obj/bus-fpic.o - @echo AR $@ - @mkdir -p bin - @ar rcs $@ $^ - -bin/bus: obj/cmdline-nofpic.o obj/bus-nofpic.o - @echo CC -o $@ - @mkdir -p bin - @${CC} ${FLAGS} -lrt -o $@ $^ ${LDFLAGS} - -bin/libbus.so.${LIB_VERSION}: obj/bus-fpic.o - @echo CC -o $@ - @mkdir -p bin - @${CC} ${FLAGS} -lrt -shared -Wl,-soname,libbus.so.${LIB_MAJOR} -o $@ $^ ${LDFLAGS} - -bin/libbus.so.${LIB_MAJOR}: - @echo LN -s $@ - @mkdir -p bin - @ln -sf libbus.so.${LIB_VERSION} $@ - -bin/libbus.so: - @echo LN -s $@ - @mkdir -p bin - @ln -sf libbus.so.${LIB_VERSION} $@ - -obj/%-nofpic.o: src/%.c src/*.h - @echo CC -c $@ - @mkdir -p obj - @${CC} ${FLAGS} -c -o $@ ${CPPFLAGS} ${CFLAGS} $< - -obj/%-fpic.o: src/%.c src/*.h - @echo CC -c $@ - @mkdir -p obj - @${CC} ${FLAGS} -fPIC -c -o $@ ${CPPFLAGS} ${CFLAGS} $< - -bin/%.info: doc/info/%.texinfo - @echo MAKEINFO $@ - @mkdir -p bin - @$(MAKEINFO) $< - @mv $*.info $@ - -bin/%.pdf: doc/info/%.texinfo - @echo TEXI2PDF $@ - @! test -d obj/pdf || rm -rf obj/pdf - @mkdir -p bin obj/pdf - @cd obj/pdf && texi2pdf ../../"$<" < /dev/null - @mv obj/pdf/$*.pdf $@ - -bin/%.dvi: doc/info/%.texinfo - @echo TEXI2DVI $@ - @! test -d obj/dvi || rm -rf obj/dvi - @mkdir -p bin obj/dvi - @cd obj/dvi && $(TEXI2DVI) ../../"$<" < /dev/null - @mv obj/dvi/$*.dvi $@ - -bin/%.ps: doc/info/%.texinfo - @echo TEXI2PS $@ - @! test -d obj/ps || rm -rf obj/ps - @mkdir -p bin obj/ps - @cd obj/ps && texi2pdf --ps ../../"$<" < /dev/null - @mv obj/ps/$*.ps $@ - - - -install: install-bus install-man install-info install-examples -install-all: install-bus install-doc -install-lib: install-so install-a install-h -install-doc: install-man install-info install-pdf install-dvi install-ps install-examples -install-man: install-man1 install-man3 install-man5 install-man7 -install-bus: install-bin install-lib install-license - -install-bin: bin/bus - @echo INSTALL bus - @install -dm755 -- "${DESTDIR}${BINDIR}" - @install -m755 $^ -- "${DESTDIR}${BINDIR}" - -install-so: bin/libbus.so.${LIB_VERSION} - @echo INSTALL libbus.so - @install -dm755 -- "${DESTDIR}${LIBDIR}" - @install -m755 $^ -- "${DESTDIR}${LIBDIR}" - @ln -sf -- "libbus.so.${LIB_VERSION}" "${DESTDIR}${LIBDIR}/libbus.so.${LIB_MAJOR}" - @ln -sf -- "libbus.so.${LIB_VERSION}" "${DESTDIR}${LIBDIR}/libbus.so" - -install-a: bin/libbus.a - @echo INSTALL libbus.a - @install -dm755 -- "${DESTDIR}${LIBDIR}" - @install -m644 $^ -- "${DESTDIR}${LIBDIR}" - -install-h: - @echo INSTALL bus.h - @install -dm755 -- "${DESTDIR}${INCLUDEDIR}" - @install -m644 src/bus.h -- "${DESTDIR}${INCLUDEDIR}" - -install-license: - @echo INSTALL LICENSE - @install -dm755 -- "${DESTDIR}${LICENSEDIR}/${PKGNAME}" - @install -m644 LICENSE -- "${DESTDIR}${LICENSEDIR}/${PKGNAME}" - -install-man1: $(foreach M,${MAN1},bin/${M}.1) - @echo 'INSTALL *.1' - @install -dm755 -- "${DESTDIR}${MANDIR}/man1" - @install -m644 $^ -- "${DESTDIR}${MANDIR}/man1" - -install-man3: $(foreach M,${MAN3},bin/${M}.3) - @echo 'INSTALL *.3' - @install -dm755 -- "${DESTDIR}${MANDIR}/man3" - @install -m644 $^ -- "${DESTDIR}${MANDIR}/man3" - @ln -sf -- "bus_poll.3" "${DESTDIR}${MANDIR}/man3/bus_poll_start.3" - @ln -sf -- "bus_poll.3" "${DESTDIR}${MANDIR}/man3/bus_poll_stop.3" - @ln -sf -- "bus_poll.3" "${DESTDIR}${MANDIR}/man3/bus_poll_timed.3" - @ln -sf -- "bus_read.3" "${DESTDIR}${MANDIR}/man3/bus_read_timed.3" - @ln -sf -- "bus_write.3" "${DESTDIR}${MANDIR}/man3/bus_write_timed.3" - -install-man5: $(foreach M,${MAN5},bin/${M}.5) - @echo 'INSTALL *.5' - @install -dm755 -- "${DESTDIR}${MANDIR}/man5" - @install -m644 $^ -- "${DESTDIR}${MANDIR}/man5" - -install-man7: $(foreach M,${MAN7},bin/${M}.7) - @echo 'INSTALL *.7' - @install -dm755 -- "${DESTDIR}${MANDIR}/man7" - @install -m644 $^ -- "${DESTDIR}${MANDIR}/man7" - -install-info: bin/bus.info - @echo INSTALL bus.info - @install -dm755 -- "$(DESTDIR)$(INFODIR)" - @install -m644 $< -- "$(DESTDIR)$(INFODIR)/$(PKGNAME).info" - -install-pdf: bin/bus.pdf - @echo INSTALL bus.pdf - @install -dm755 -- "$(DESTDIR)$(DOCDIR)" - @install -m644 $< -- "$(DESTDIR)$(DOCDIR)/$(PKGNAME).pdf" - -install-dvi: bin/bus.dvi - @echo INSTALL bus.dvi - @install -dm755 -- "$(DESTDIR)$(DOCDIR)" - @install -m644 $< -- "$(DESTDIR)$(DOCDIR)/$(PKGNAME).dvi" - -install-ps: bin/bus.ps - @echo INSTALL bus.ps - @install -dm755 -- "$(DESTDIR)$(DOCDIR)" - @install -m644 $< -- "$(DESTDIR)$(DOCDIR)/$(PKGNAME).ps" - -install-examples: - @echo INSTALL examples - @install -dm755 -- $(foreach E,$(EXAMPLES),"$(DESTDIR)$(DOCDIR)/$(PKGNAME)/examples/$(E)") - @$(foreach E,$(EXAMPLES),cp -- $(foreach F,$(EXAMPLE_$(E)),doc/examples/$(E)/$(F)) \ - "$(DESTDIR)$(DOCDIR)/$(PKGNAME)/examples/$(E)" &&) true - - +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +LIB_MAJOR = 3 +LIB_MINOR = 1 +LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR) +VERSION = 3.1.7 + +MAN1 = bus.1 bus-broadcast.1 bus-create.1 bus-listen.1 bus-remove.1 bus-wait.1 bus-chmod.1 bus-chown.1 bus-chgrp.1 +MAN3 = bus_create.3 bus_unlink.3 bus_open.3 bus_close.3 bus_read.3 bus_write.3 bus_poll.3 bus_chmod.3 bus_chown.3 +MAN5 = bus.5 +MAN7 = libbus.7 + +LOBJ = libbus.lo +OBJ = bus.o libbus.o +HDR = bus.h arg.h + +all: bus libbus.a libbus.so + +$(OBJ): $(@:.o=.c) $(HDR) +$(LOBJ): $(@:.lo=.c) $(HDR) + +bus: $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) + +.o.a: + $(AR) $(ARFLAGS) $@ $< + +.c.lo: + $(CC) $(CFLAGS) -fPIC -c -o $@ $< + +.lo.so: + $(CC) -shared -Wl,-soname,$@.$(LIB_MAJOR) -o $@ $< $(LDFLAGS) + +bus.pdf: bus.texinfo fdl.texinfo + texi2pdf bus.texinfo < /dev/null + +install: bus libbus.a libbus.so + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" + mkdir -p -- "$(DESTDIR)$(PREFIX)/include" + mkdir -p -- "$(DESTDIR)$(PREFIX)/licenses/bus" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man3" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man5" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man7" + cp -- bus "$(DESTDIR)$(PREFIX)/bin" + cp -- libbus.a "$(DESTDIR)$(PREFIX)/lib" + cp -- libbus.so "$(DESTDIR)$(PREFIX)/lib/libbus.so.$(LIB_VERSION)" + cp -- bus.h "$(DESTDIR)$(PREFIX)/include" + cp -- LICENSE "$(DESTDIR)$(PREFIX)/licenses/bus" + ln -sf -- libbus.so.$(LIB_VERSION) "$(DESTDIR)$(PREFIX)/lib/libbus.so.$(LIB_MAJOR)" + ln -sf -- libbus.so.$(LIB_VERSION) "$(DESTDIR)$(PREFIX)/lib/libbus.so" + cp -- $(MAN1) "$(DESTDIR)$(MANPREFIX)/man1" + cp -- $(MAN3) "$(DESTDIR)$(MANPREFIX)/man3" + cp -- $(MAN5) "$(DESTDIR)$(MANPREFIX)/man5" + cp -- $(MAN7) "$(DESTDIR)$(MANPREFIX)/man7" + ln -sf -- bus_poll.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_start.3" + ln -sf -- bus_poll.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_stop.3" + ln -sf -- bus_poll.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_timed.3" + ln -sf -- bus_read.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_read_timed.3" + ln -sf -- bus_write.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_write_timed.3" uninstall: - -rm -- "${DESTDIR}${BINDIR}/bus" - -rm -- "${DESTDIR}${LIBDIR}/libbus.so.${LIB_VERSION}" - -rm -- "${DESTDIR}${LIBDIR}/libbus.so.${LIB_MAJOR}" - -rm -- "${DESTDIR}${LIBDIR}/libbus.so" - -rm -- "${DESTDIR}${LIBDIR}/libbus.a" - -rm -- "${DESTDIR}${INCLUDEDIR}/bus.h" - -rm -- "${DESTDIR}${LICENSEDIR}/${PKGNAME}/LICENSE" - -rmdir -- "${DESTDIR}${LICENSEDIR}/${PKGNAME}" - -rm -- $(foreach M,${MAN1},"${DESTDIR}${MANDIR}/man1/${M}.1") - -rm -- $(foreach M,${MAN3},"${DESTDIR}${MANDIR}/man3/${M}.3") - -rm -- "${DESTDIR}${MANDIR}/man3/bus_poll_start.3" - -rm -- "${DESTDIR}${MANDIR}/man3/bus_poll_stop.3" - -rm -- "${DESTDIR}${MANDIR}/man3/bus_poll_timed.3" - -rm -- "${DESTDIR}${MANDIR}/man3/bus_read_timed.3" - -rm -- "${DESTDIR}${MANDIR}/man3/bus_write_timed.3" - -rm -- $(foreach M,${MAN5},"${DESTDIR}${MANDIR}/man5/${M}.5") - -rm -- $(foreach M,${MAN7},"${DESTDIR}${MANDIR}/man7/${M}.7") - -rm -- "$(DESTDIR)$(INFODIR)/$(PKGNAME).info" - -rm -- "$(DESTDIR)$(DOCDIR)/$(PKGNAME).pdf" - -rm -- "$(DESTDIR)$(DOCDIR)/$(PKGNAME).dvi" - -rm -- "$(DESTDIR)$(DOCDIR)/$(PKGNAME).ps" - -$(foreach E,$(EXAMPLES),rm -- \ - $(foreach F,$(EXAMPLE_$(E)),"$(DESTDIR)$(DOCDIR)/$(PKGNAME)/examples/$(E)/$(F)");) - -rmdir -- $(foreach E,$(EXAMPLES),"$(DESTDIR)$(DOCDIR)/$(PKGNAME)/examples/$(E)") - - + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/bus" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libbus.a" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libbus.so.$(LIB_VERSION)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libbus.so.$(LIB_MAJOR)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libbus.so" + -rm -f -- "$(DESTDIR)$(PREFIX)/include/bus.h" + -rm -rf -- "$(DESTDIR)$(PREFIX)/licenses/bus" + -cd "$(DESTDIR)$(MANPREFIX)/man1" && rm -f -- $(MAN1) + -cd "$(DESTDIR)$(MANPREFIX)/man3" && rm -f -- $(MAN3) + -cd "$(DESTDIR)$(MANPREFIX)/man5" && rm -f -- $(MAN5) + -cd "$(DESTDIR)$(MANPREFIX)/man7" && rm -f -- $(MAN7) + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_start.3" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_stop.3" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_timed.3" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_read_timed.3" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_write_timed.3" clean: - @echo cleaning - @-rm -rf obj bin - - + -rm -f -- bus *.o *.lo *.a *.so *.log *.toc *.aux *.pdf -.PHONY: default all doc bin bus lib so a man man1 man3 man5 man7 info pdf dvi \ - ps install install-all install-doc install-man install-bin install-so \ - install-a install-h install-lib install-license install-man1 install-bus \ - install-man3 install-man5 install-man7 install-info install-pdf install-dvi \ - install-ps install-examples uninstall clean +.SUFFIXES: +.SUFFIXES: .so .a .o .lo .c .pdf +.PHONY: all install uninstall clean diff --git a/README b/README index 7092f4c..1f6f48f 100644 --- a/README +++ b/README @@ -39,6 +39,3 @@ SEE ALSO pthread_spin_destroy(3), pthread_cond_destroy(3), lockf(3), flock(2), fcntl(3), ioctl(3), mkfifo(3), rendezvous(2), 9p(2), libdoor(3), python-bus - - Full documentation available locally via: info '(bus)' - diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..09a5350 --- /dev/null +++ b/arg.h @@ -0,0 +1,38 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][0] == '-') {\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } else {\ + break;\ + }\ + } + +#endif diff --git a/bus-broadcast.1 b/bus-broadcast.1 new file mode 100644 index 0000000..94a05ec --- /dev/null +++ b/bus-broadcast.1 @@ -0,0 +1,26 @@ +.TH BUS-BROADCAST 1 BUS +.SH NAME +bus broadcast - Broadcast a message on a bus +.SH SYNOPSIS +.B bus broadcast +[-n] +.IR pathname +.IR message +.SH DESCRIPTION +Broadcast \fImessage\fP on the bus associated with \fIpathname\fP. +.SH OPTIONS +.TP +.B \-n +Fail if another process is attempting to broadcast on the bus. +.SH EXIT STATUS +.TP +0 +The command was successful. +.TP +1 +The command failed. +.TP +2 +The command is not recognised. +.SH SEE ALSO +.BR bus (5) diff --git a/bus-chgrp.1 b/bus-chgrp.1 new file mode 100644 index 0000000..b871c41 --- /dev/null +++ b/bus-chgrp.1 @@ -0,0 +1,29 @@ +.TH BUS-CHGRP 1 BUS +.SH NAME +bus chgrp - Change group ownership of a bus +.SH SYNOPSIS +.B bus chgrp +.IR group +.IR pathname +.SH DESCRIPTION +Change the group, that owns a bus with an associated \fIpathname\fP, +to the specified \fIgroup\fP. The \fIgroup\fP can be specified either +with a GID or with a group name. +.PP +The current ownership of a bus can be retrieved by running +.BR stat (1) +over the \fIpathname\fP of the bus. +.SH EXIT STATUS +.TP +0 +The command was successful. +.TP +1 +The command failed. +.TP +2 +The command is not recognised. +.SH SEE ALSO +.BR bus-chown (1), +.BR bus-chmod (1), +.BR stat (1) diff --git a/bus-chmod.1 b/bus-chmod.1 new file mode 100644 index 0000000..51ee9b4 --- /dev/null +++ b/bus-chmod.1 @@ -0,0 +1,40 @@ +.TH BUS-CHMOD 1 BUS +.SH NAME +bus chmod - Change permissions on a bus +.SH SYNOPSIS +.B bus chmod +.IR permissions +.IR pathname +.SH DESCRIPTION +Change who have access to a bus with an associated \fIpathname\fP. +In the \fIpermissions\fP, the owner, the group, and others (not +in group) are represented by the symbols \fBu\fP, \fBg\fP, and +\fBo\fP, respectively. The \fIpermissions\fP string is imagined +to have always be prefixed with an \fB=\fP. This symbols means +that all user classes list after it, and only those classes, as +permission to use the bus. Similarly the symbols \fB+\fP and +\fB\-\fP can be used to grant and revoke access, respectively. +The symbols \fB=\fP, \fB+\fP, and \fB\-\fP can be mixed, and are +interpreted from left to right. Alternatively the \fIpermissions\fP +string can be a octal number, where the owner is represented by any +bit in 700 (100, 200, or 400, or any combination thereof), the +group is represented by any bit in 70, and others (not in the group) +is represented by any bit in 7. +.PP +The current permissions of a bus can be retrieved by running +.BR stat (1) +over the \fIpathname\fP of the bus. +.SH EXIT STATUS +.TP +0 +The command was successful. +.TP +1 +The command failed. +.TP +2 +The command is not recognised. +.SH SEE ALSO +.BR bus-chown (1), +.BR bus-chgrp (1), +.BR stat (1) diff --git a/bus-chown.1 b/bus-chown.1 new file mode 100644 index 0000000..c6d9056 --- /dev/null +++ b/bus-chown.1 @@ -0,0 +1,32 @@ +.TH BUS-CHOWN 1 BUS +.SH NAME +bus chown - Change ownership of a bus +.SH SYNOPSIS +.B bus chown +.IR owner [\fB:\fP group ] +.IR pathname +.SH DESCRIPTION +Change the owner, that owns a bus with an associated \fIpathname\fP, +to the specified \fIowner\fP. The \fIowner\fP can be specified either +with a UID or with a user name. If a \fIgroup\fP is specified, the +bus's owner-group will be set to that \fIgroup\fP, otherwise the group +will remain unchanged. The \fIgroup\fP can be specified either with +a GID or with a group name. +.PP +The current ownership of a bus can be retrieved by running +.BR stat (1) +over the \fIpathname\fP of the bus. +.SH EXIT STATUS +.TP +0 +The command was successful. +.TP +1 +The command failed. +.TP +2 +The command is not recognised. +.SH SEE ALSO +.BR bus-chgrp (1), +.BR bus-chmod (1), +.BR stat (1) diff --git a/bus-create.1 b/bus-create.1 new file mode 100644 index 0000000..2d34b83 --- /dev/null +++ b/bus-create.1 @@ -0,0 +1,28 @@ +.TH BUS-CREATE 1 BUS +.SH NAME +bus create - Create a bus +.SH SYNOPSIS +.B bus create +[-x] +.IR [pathname] +.SH DESCRIPTION +Create a bus with an associated \fIpathname\fP. If \fIpathname\fP +is omitted, a random pathname in \fI$XDG_RUNTIME_DIR/bus\fP will be +created and printed to stdout. +.SH OPTIONS +.TP +.B \-x +Fail if the \fIpathname\fP already exists. +.SH EXIT STATUS +.TP +0 +The command was successful. +.TP +1 +The command failed. +.TP +2 +The command is not recognised. +.SH SEE ALSO +.BR bus (5), +.BR bus-remove (1) diff --git a/bus-listen.1 b/bus-listen.1 new file mode 100644 index 0000000..75a6106 --- /dev/null +++ b/bus-listen.1 @@ -0,0 +1,23 @@ +.TH BUS-LISTEN 1 BUS +.SH NAME +bus listen - Listen for new messages on a bus +.SH SYNOPSIS +.B bus listen +.IR pathname +.IR command +.SH DESCRIPTION +Listen for new messages on the bus associated with \fIpathname\fP. Once +a message is received, \fIcommand\fP will be spawned with \fI$msg\fP set +to the received message. POSIX shell syntax applies to \fIcommand\fP. +.SH EXIT STATUS +.TP +0 +The command was successful. +.TP +1 +The command failed. +.TP +2 +The command is not recognised. +.SH SEE ALSO +.BR bus (5) diff --git a/bus-remove.1 b/bus-remove.1 new file mode 100644 index 0000000..5905270 --- /dev/null +++ b/bus-remove.1 @@ -0,0 +1,20 @@ +.TH BUS-REMOVE 1 BUS +.SH NAME +bus remove - Remove a bus +.SH SYNOPSIS +.B bus remove +.IR pathname +.SH DESCRIPTION +Remove the bus associated with \fIpathname\fP. +.SH EXIT STATUS +.TP +0 +The command was successful. +.TP +1 +The command failed. +.TP +2 +The command is not recognised. +.SH SEE ALSO +.BR bus (5) diff --git a/bus-wait.1 b/bus-wait.1 new file mode 100644 index 0000000..07d5704 --- /dev/null +++ b/bus-wait.1 @@ -0,0 +1,24 @@ +.TH BUS-WAIT 1 BUS +.SH NAME +bus wait - Listen for a new message on a bus +.SH SYNOPSIS +.B bus wait +.IR pathname +.IR command +.SH DESCRIPTION +Listen for a new message on the bus associated with \fIpathname\fP, stop +listening once a message has been received. Once a message is received, +\fIcommand\fP will be spawned with \fI$msg\fP set to the received +message. POSIX shell syntax applies to \fIcommand\fP. +.SH EXIT STATUS +.TP +0 +The command was successful. +.TP +1 +The command failed. +.TP +2 +The command is not recognised. +.SH SEE ALSO +.BR bus (5) diff --git a/bus.1 b/bus.1 new file mode 100644 index 0000000..fc75189 --- /dev/null +++ b/bus.1 @@ -0,0 +1,69 @@ +.TH BUS 1 BUS +.SH NAME +bus - A simple daemonless system for broadcasting messages locally +.SH SYNOPSIS +.B bus +.IR command +.IR argument\ ... +.SH COMMANDS +.TP +.B create +Create a bus, see +.BR bus-create (1) +for further details. +.TP +.B remove +Remove a bus, see +.BR bus-remove (1) +for further details. +.TP +.B listen +Listen for new message on a bus, see +.BR bus-listen (1) +for further details. +.TP +.B wait +Listen for one new message only on a bus, see +.BR bus-wait (1) +for further details. +.TP +.B broadcast +Broadcast a message on a bus, see +.BR bus-broadcast (1) +for further details. +.TP +.B chmod +Change permissions on a bus, see +.BR bus-chmod (1) +for further details. +.TP +.B chown +Change ownership of a bus, see +.BR bus-chown (1) +for further details. +.TP +.B chgrp +Change group ownership of a bus, see +.BR bus-chgrp (1) +for further details. +.SH EXIT STATUS +.TP +0 +The command was successful. +.TP +1 +The command failed. +.TP +2 +The command is not recognised. +.SH SEE ALSO +.BR bus-create (1), +.BR bus-remove (1), +.BR bus-listen (1), +.BR bus-wait (1), +.BR bus-broadcast (1), +.BR bus-chmod (1), +.BR bus-chown (1), +.BR bus-chgrp (1), +.BR bus (5), +.BR libbus (7) diff --git a/bus.5 b/bus.5 new file mode 100644 index 0000000..213f2d2 --- /dev/null +++ b/bus.5 @@ -0,0 +1,42 @@ +.TH BUS 5 BUS +.SH NAME +bus - A simple daemonless system for broadcasting messages locally +.SH DESCRIPTION +\fBbus\fP is a simple interprocess communication system for broadcasting +messages to other processes on the same machine. \fBbus\fP does not use +any daemon. Instead, all communication and synchronisation is managed +using System V (XSI) semaphores and System V (XSI) shared memory. +.PP +The command \fBbus create\fP can be used to create new buses. By +convention, buses should be stored in \fI$XDG_RUNTIME_DIR/bus\fP, this is +what \fBbus create\fP does if no pathname is given. The pathname of the +bus should be tracked using \fIBUS_X\fP, where \fIX\fP is replaced with +either: +.TP +.B GENERIC +For the bus used in generic cases. That is all but the cases of the +buses listed below. +.TP +.B AUDIO +For the bus used in with the audio subsystem is involved. +.TP +.B VIDEO +For the bus used in with the video subsystem is involved. +.TP +.B INPUT +For the bus used in with the input subsystem is involved. +.TP +.B FILES +For the bus used in with the storage subsystem is involved. +.PP +Messages broadcasted on a bus cannot be longer than 2047 bytes, +excluding NULL termination. Message should be encoded in UTF-8, +and most not contain the NULL character. +.PP +Broadcasted message should start with the process ID, or 0 if ephemeral, +whence the message originated, followed by a single regular space. +.SH SEE ALSO +.BR bus (1), +.BR libbus (7), +.BR semop (2), +.BR shmop (2) diff --git a/bus.c b/bus.c new file mode 100644 index 0000000..dc72a17 --- /dev/null +++ b/bus.c @@ -0,0 +1,385 @@ +/* See LICENSE file for copyright and license details. */ +#include "bus.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arg.h" + + + +/** + * Statement wrapper that goes to `fail` on failure + */ +#define t(inst) do { if ((inst) == -1) goto fail; } while (0) + + + +/** + * The name of the process + */ +char *argv0; + +/** + * The command to spawn when a message is received + */ +static const char *command; + + + +/** + * Spawn a command because a message has been received + * + * @param message The received message + * @param user_data Not used + * @return 1 (continue listening) on success, -1 on error + */ +static int +spawn_continue(const char *message, void *user_data) +{ + pid_t pid; + if (!message) + return 1; + if ((pid = fork())) + return pid == -1 ? -1 : 1; + setenv("msg", message, 1); + execlp("sh", "sh", "-c", command, NULL); + perror(argv0); + exit(1); + (void) user_data; +} + + +/** + * Spawn a command because a message has been received + * + * @param message The received message + * @param user_data Not used + * @return 0 (stop listening) on success, -1 on error, or 1 if `message` is `NULL` + */ +static int +spawn_break(const char *message, void *user_data) +{ + pid_t pid; + if (!message) + return 1; + if ((pid = fork())) + return pid == -1 ? -1 : 0; + setenv("msg", message, 1); + execlp("sh", "sh", "-c", command, NULL); + perror(argv0); + exit(1); + (void) user_data; +} + + +/** + * Parse a permission string + * + * @param str The permission string + * @param andnot Output paramter for the mask of bits to remove (before applying `*or`) + * @param or Output paramter for the mask of bits to apply + * @return 0 on success, -1 on error + */ +static int +parse_mode(const char *str, mode_t *andnot, mode_t *or) +{ +#define U S_IRWXU +#define G S_IRWXG +#define O S_IRWXO + const char *s = str; + int numerical = 1; + mode_t mode = 0; + char op = '='; + mode_t bits; + + *andnot = 0; + *or = 0; + + if (!*s) + return errno = 0, -1; + + for (s = str; *s; s++) { + if (('0' > *s) || (*s > '7')) { + numerical = 0; + break; + } else { + mode = (mode << 3) | (*s & 15); + } + } + + if (numerical) { + *andnot = U | G | O; + *or = mode; + *or &= U | G | O; + *or = (*or & U) ? (*or | U) : (*or & (mode_t)~U); + *or = (*or & G) ? (*or | G) : (*or & (mode_t)~G); + *or = (*or & O) ? (*or | O) : (*or & (mode_t)~O); + return 0; + } + + for (s = str; *s; s++) { + if (strchr("+-=", *s)) { + op = *s; + } else if (strchr("ugo", *s)) { + if (*s == 'u') + bits = U; + else if (*s == 'g') + bits = G; + else + bits = O; + if (op == '+') { + *andnot |= bits; + *or |= bits; + } + else if (op == '-') { + *andnot |= bits; + *or &= ~bits; + } + else if (op == '=') { + *andnot |= U | G | O; + *or |= bits; + } + } else { + return errno = 0, -1; + } + } + + return 0; +} + + +/** + * Parse a user name/identifier string + * + * @param str The user's name or identifier + * @param uid Output parameter for the user's identifier + * @return 0 on success, -1 on error + */ +static int +parse_uid(const char *str, uid_t *uid) +{ + const char *s = str; + int numerical = 1; + uid_t rc = 0; + struct passwd *pwd; + + if (!*s || (*s == ':')) + return errno = 0, -1; + + for (s = str; *s; s++) { + if (('0' > *s) || (*s > '9')) { + numerical = 0; + break; + } + } + + if (numerical) { + for (s = str; *s; s++) + rc = (rc * 10) + (*s & 15); + *uid = rc; + return 0; + } + + pwd = getpwnam(str); + if (!pwd) { + return -1; + } + *uid = pwd->pw_uid; + return 0; +} + + +/** + * Parse a group name/identifier string + * + * @param str The group's name or identifier + * @param gid Output parameter for the group's identifier + * @return 0 on success, -1 on error + */ +static int +parse_gid(const char *str, gid_t *gid) +{ + const char *s = str; + int numerical = 1; + gid_t rc = 0; + struct group *grp; + + if (!*s || strchr(s, ':')) + return errno = 0, -1; + + for (s = str; *s; s++) { + if (('0' > *s) || (*s > '9')) { + numerical = 0; + break; + } + } + + if (numerical) { + for (s = str; *s; s++) + rc = (rc * 10) + (*s & 15); + *gid = rc; + return 0; + } + + grp = getgrnam(str); + if (!grp) + return -1; + *gid = grp->gr_gid; + return 0; +} + + +/** + * Parse a ownership string + * + * @param str The ownership string + * @param uid Output parameter for the owner, `NULL` if `str` only contains the group + * @param gid Output parameter for the group, `NULL` if `str` only contains the owner + * @return 0 on success, -1 on error + */ +static int +parse_owner(char *str, uid_t *uid, gid_t *gid) +{ + int r = 0; + char* group; + + if (!uid) + return parse_gid(str, gid); + if (!gid) + return parse_uid(str, uid); + + group = strchr(str, ':'); + *group++ = 0; + + r = parse_gid(group, gid); + if (r) + return r; + return parse_uid(str, uid); +} + + + +/** + * Main function of the command line interface for the bus system + * + * @param argc The number of elements in `argv` + * @param argv The command. Valid commands: + * create [-x] [--] [] # create a bus + * remove [--] # remove a bus + * listen [--] # listen for new messages + * wait [--] # listen for one new message + * broadcast [-n] [--] # broadcast a message + * chmod [--] # change permissions + * chown [--] [:] # change ownership + * chgrp [--] # change group + * will be spawned with $arg set to the message + * @return 0 on sucess, 1 on error, 2 on invalid command + */ +int +main(int argc, char *argv[]) +{ + int xflag = 0; + int nflag = 0; + bus_t bus; + char *file; + struct stat attr; + uid_t uid; + gid_t gid; + mode_t mode_andnot, mode_or; + + /* Parse arguments. */ + ARGBEGIN { + case 'x': + xflag = 1; + break; + case 'n': + nflag = 1; + break; + default: + return 2; + } ARGEND; + + /* Check options. */ + if (xflag && strcmp(argv[0], "create") && (argc != 2)) + return 2; + if (nflag && strcmp(argv[0], "broadcast") && (argc != 3)) + return 2; + + /* Create a new bus with selected name. */ + if ((argc == 2) && !strcmp(argv[0], "create")) { + t(bus_create(argv[1], xflag * BUS_EXCL, NULL)); + + /* Create a new bus with random name. */ + } else if ((argc == 1) && !strcmp(argv[0], "create")) { + t(bus_create(NULL, 0, &file)); + printf("%s\n", file); + free(file); + + /* Remove a bus. */ + } else if ((argc == 2) && !strcmp(argv[0], "remove")) { + t(bus_unlink(argv[1])); + + /* Listen on a bus in a loop. */ + } else if ((argc == 3) && !strcmp(argv[0], "listen")) { + command = argv[2]; + t(bus_open(&bus, argv[1], BUS_RDONLY)); + t(bus_read(&bus, spawn_continue, NULL)); + t(bus_close(&bus)); + + /* Listen on a bus for one message. */ + } else if ((argc == 3) && !strcmp(argv[0], "wait")) { + command = argv[2]; + t(bus_open(&bus, argv[1], BUS_RDONLY)); + t(bus_read(&bus, spawn_break, NULL)); + t(bus_close(&bus)); + + /* Broadcast a message on a bus. */ + } else if ((argc == 3) && !strcmp(argv[0], "broadcast")) { + t(bus_open(&bus, argv[1], BUS_WRONLY)); + t(bus_write(&bus, argv[2], nflag * BUS_NOWAIT)); + t(bus_close(&bus)); + + /* Change permissions. */ + } else if ((argc == 3) && !strcmp(argv[0], "chmod")) { + t(parse_mode(argv[1], &mode_andnot, &mode_or)); + t(stat(argv[2], &attr)); + attr.st_mode &= ~mode_andnot; + attr.st_mode |= mode_or; + t(bus_chmod(argv[2], attr.st_mode)); + + /* Change ownership. */ + } else if ((argc == 3) && !strcmp(argv[0], "chown")) { + if (strchr(argv[1], ':')) { + t(parse_owner(argv[1], &uid, &gid)); + t(bus_chown(argv[2], uid, gid)); + } else { + t(parse_owner(argv[1], &uid, NULL)); + t(stat(argv[2], &attr)); + t(bus_chown(argv[2], uid, attr.st_gid)); + } + + /* Change group. */ + } else if ((argc == 3) && !strcmp(argv[0], "chgrp")) { + t(parse_owner(argv[1], NULL, &gid)); + t(stat(argv[2], &attr)); + t(bus_chown(argv[2], attr.st_uid, gid)); + + } else + return 2; + + return 0; + +fail: + if (!errno) + return 2; + perror(argv0); + return 1; +} diff --git a/bus.h b/bus.h new file mode 100644 index 0000000..707c9e4 --- /dev/null +++ b/bus.h @@ -0,0 +1,325 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef BUS_H +#define BUS_H + + +#ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE +#endif +#include +#include + + + +#if defined(__GNUC__) +# define BUS_COMPILER_GCC(X) X +#else +# define BUS_COMPILER_GCC(X) /* ignore */ +#endif + + + +/** + * Open the bus for reading only + */ +#define BUS_RDONLY 1 + +/** + * Open the bus for writing only + */ +#define BUS_WRONLY 0 + +/** + * Open the bus for both reading and writing only + */ +#define BUS_RDWR 0 + +/** + * Fail to create bus if its file already exists + */ +#define BUS_EXCL 2 + +/** + * Fail if interrupted + */ +#define BUS_INTR 4 + +/** + * Function shall fail with errno set to `EAGAIN` + * if the it would block and this flag is used + */ +#define BUS_NOWAIT 1 + + + +/** + * The number of bytes in storeable in the shared memory, + * note that this includes the NUL-termination. + * This means that message can be at most one byte smaller. + */ +#define BUS_MEMORY_SIZE 2048 + + + +/** + * Bus information + */ +typedef struct bus +{ + /** + * The key for the semaphore array + */ + key_t key_sem; + + /** + * The key for the shared memory + */ + key_t key_shm; + + /** + * The ID of the semaphore array + */ + int sem_id; + + /** + * The address of the shared memory + */ + char *message; + + /** + * Non-zero if and only if `bus_poll` has not been + * called since the last `bus_poll_start`, or + * if `bus_poll` failed during reading + */ + int first_poll; + +} bus_t; + + + +/** + * Create a new bus + * + * @param file The pathname of the bus, `NULL` to create a random one + * @param flags `BUS_EXCL` (if `file` is not `NULL`) to fail if the file + * already exists, otherwise if the file exists, nothing + * will happen; + * `BUS_INTR` to fail if interrupted + * @param out_file Output parameter for the pathname of the bus + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__warn_unused_result__))) +int bus_create(const char *restrict, int, char **restrict); + +/** + * Remove a bus + * + * @param file The pathname of the bus + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__))) +int bus_unlink(const char *); + + +/** + * Open an existing bus + * + * @param bus Bus information to fill + * @param file The filename of the bus + * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR`, + * the value must not be negative + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) +int bus_open(bus_t *restrict, const char *restrict, int); + +/** + * Close a bus + * + * @param bus Bus information + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__))) +int bus_close(bus_t *); + + +/** + * Broadcast a message on a bus + * + * @param bus Bus information + * @param message The message to write, may not be longer than + * `BUS_MEMORY_SIZE` including the NUL-termination + * @param flags `BUS_NOWAIT` if this function shall fail if + * another process is currently running this + * procedure + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) +int bus_write(const bus_t *, const char *, int); + +/** + * Broadcast a message on a bus + * + * @param bus Bus information + * @param message The message to write, may not be longer than + * `BUS_MEMORY_SIZE` including the NUL-termination + * @param timeout The time the operation shall fail with errno set + * to `EAGAIN` if not completed + * @param clockid The ID of the clock the `timeout` is measured with, + * it most be a predictable clock + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))) +int bus_write_timed(const bus_t *, const char *, const struct timespec *, clockid_t); + + +/** + * Listen (in a loop, forever) for new message on a bus + * + * @param bus Bus information + * @param callback Function to call when a message is received, the + * (message, user_data) input parameters will be the read message and + * `user_data` from `bus_read`'s parameter with the + * same name. The message must have been parsed or + * copied when `callback` returns as it may be over + * overridden after that time. `callback` should + * return either of the the values: + * * 0: stop listening + * * 1: continue listening + * * -1: an error has occurred + * However, the function [`bus_read`] will invoke + * `callback` with `message` set to `NULL`one time + * directly after it has started listening on the + * bus. This is to the the program now it can safely + * continue with any action that requires that the + * programs is listening on the bus. + * @param user_data Parameter passed to `callback` + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))) +int bus_read(const bus_t *restrict, int (*)(const char *, void *), void *); + +/** + * Listen (in a loop, forever) for new message on a bus + * + * @param bus Bus information + * @param callback Function to call when a message is received, the + * (message, user_data) input parameters will be the read message and + * `user_data` from `bus_read`'s parameter with the + * same name. The message must have been parsed or + * copied when `callback` returns as it may be over + * overridden after that time. `callback` should + * return either of the the values: + * * 0: stop listening + * * 1: continue listening + * * -1: an error has occurred + * However, the function [`bus_read`] will invoke + * `callback` with `message` set to `NULL`one time + * directly after it has started listening on the + * bus. This is to the the program now it can safely + * continue with any action that requires that the + * programs is listening on the bus. + * @param user_data Parameter passed to `callback` + * @param timeout The time the operation shall fail with errno set + * to `EAGAIN` if not completed, note that the callback + * function may or may not have been called + * @param clockid The ID of the clock the `timeout` is measured with, + * it most be a predictable clock + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))) +int bus_read_timed(const bus_t *restrict, int (*)(const char *, void *), + void *, const struct timespec *, clockid_t); + + +/** + * Announce that the thread is listening on the bus. + * This is required so the will does not miss any + * messages due to race conditions. Additionally, + * not calling this function will cause the bus the + * misbehave, is `bus_poll` is written to expect + * this function to have been called. + * + * @param bus Bus information + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) +int bus_poll_start(bus_t *); + +/** + * Announce that the thread has stopped listening on the bus. + * This is required so that the thread does not cause others + * to wait indefinitely. + * + * @param bus Bus information + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) +int bus_poll_stop(const bus_t *); + +/** + * Wait for a message to be broadcasted on the bus. + * The caller should make a copy of the received message, + * without freeing the original copy, and parse it in a + * separate thread. When the new thread has started be + * started, the caller of this function should then + * either call `bus_poll` again or `bus_poll_stop`. + * + * @param bus Bus information + * @param flags `BUS_NOWAIT` if the bus should fail and set `errno` to + * `EAGAIN` if there isn't already a message available on the bus + * @return The received message, `NULL` on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) +const char *bus_poll(bus_t *, int); + +/** + * Wait for a message to be broadcasted on the bus. + * The caller should make a copy of the received message, + * without freeing the original copy, and parse it in a + * separate thread. When the new thread has started be + * started, the caller of this function should then + * either call `bus_poll_timed` again or `bus_poll_stop`. + * + * @param bus Bus information + * @param timeout The time the operation shall fail with errno set + * to `EAGAIN` if not completed + * @param clockid The ID of the clock the `timeout` is measured with, + * it most be a predictable clock + * @return The received message, `NULL` on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__(1), __warn_unused_result__))) +const char *bus_poll_timed(bus_t *, const struct timespec *, clockid_t); + + +/** + * Change the ownership of a bus + * + * `stat(2)` can be used of the bus's associated file to get the bus's ownership + * + * @param file The pathname of the bus + * @param owner The user ID of the bus's new owner + * @param group The group ID of the bus's new group + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) +int bus_chown(const char *, uid_t, gid_t); + +/** + * Change the permissions for a bus + * + * `stat(2)` can be used of the bus's associated file to get the bus's permissions + * + * @param file The pathname of the bus + * @param mode The permissions of the bus, any permission for a user implies + * full permissions for that user, except only the owner may + * edit the bus's associated file + * @return 0 on success, -1 on error + */ +BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) +int bus_chmod(const char *, mode_t); + + + +#endif + diff --git a/bus.texinfo b/bus.texinfo new file mode 100644 index 0000000..be50cea --- /dev/null +++ b/bus.texinfo @@ -0,0 +1,2079 @@ +\input texinfo @c -*-texinfo-*- + +@c %**start of header +@setfilename bus.info +@settitle bus +@afourpaper +@documentencoding UTF-8 +@documentlanguage en +@finalout +@c %**end of header + + +@dircategory Interprorcess Communication +@direntry +* bus: (bus). A simple daemonless system for broadcasting messages locally +@end direntry + + +@copying +Copyright @copyright{} 2015 Mattias Andrée + +@quotation +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.3 or +any later version published by the Free Software Foundation; with no +Invariant Sections, with no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license is included in the section entitled +``GNU Free Documentation License''. +@end quotation +@end copying + +@ifnottex +@node Top +@top bus -- A simple daemonless system for broadcasting messages locally +@insertcopying +@end ifnottex + +@titlepage +@title bus +@subtitle A simple daemonless system for broadcasting messages locally +@author by Mattias Andrée (maandree) + +@page +@vskip 0pt plus 1filll +@insertcopying +@page +@end titlepage + +@contents + + +@iftex +@macro xrm{} +@rm{} +@end macro +@macro xtt{} +@tt{} +@end macro +@end iftex + +@ifnottex +@macro xrm{} +@end macro +@macro xtt{} +@end macro +@end ifnottex + +@menu +* Overview:: Brief overview of @command{bus}. +* Standard:: How to use @command{bus} properly. +* Invoking:: Executing @command{bus}. +* Interface:: Using @command{libbus}. +* Protocol:: How communication over @command{bus} works internally. +* Rationale:: Why @command{bus}? +* Examples:: Usecase examples and API-demonstration. +* GNU Free Documentation License:: Copying and sharing this manual. + +@detailmenu + --- The Detailed Node Listing --- + +Invoking + +* bus create:: Create a bus. +* bus remove:: Remove a bus. +* bus listen:: Listen for new message on a bus. +* bus wait:: Listen for one new message only on a bus. +* bus broadcast:: Broadcast a message on a bus. +* bus chmod:: Change permissions on a bus. +* bus chown:: Change ownership of a bus. +* bus chgrp:: Change group ownership of a bus. + +Examples + +* Audio-volume control:: +* Telephony and music:: +* Timed:: +* Nonblocking:: +* Daemon-dependencies:: + +@end detailmenu +@end menu + + + +@node Overview +@chapter Overview + +@command{bus} is a stupid-simple, thrilless, daemonless interprocess +communication system for broadcasting messages. It is a lightweight +alternative to a two-phase interprocess flexible barrier. + +@command{bus} uses a System V semaphore array and System V shared +memory. Buses are named; the key of the semaphore array and the +shared memory is stored in a regular file. + +The shared memory used by @command{bus} is always 2048 bytes. +Additionally all messages should be encoded in UTF-8 and not contain +any NULL characters, except they @emph{must} always end with a NULL +byte. Furthermore messages should be prefixed with the process +identifer of the process whence the message originated, followed +by a space. If the process is ephemeral@footnote{The process exits +after the broadcast, or shortly thereafter.}, 0 should be used +instead of the process identifier. + +Communication over @command{bus} is synchronous. The broadcast call +does not return until all listeners have received (and copied) the +message. A malfunctioning program can lock the bus. + +This software package contains a C library and a command line +utility. The package python-bus provides a Python 3 module. + + + +@node Standard +@chapter Standard + +The command @command{bus create} can be used to create new buses. By +convention, buses should be stored in @file{$XDG_RUNTIME_DIR/bus}, +this is what @command{bus create} does if no pathname is given. The +pathname of the bus should be tracked using @env{BUS_X}, where @env{X} +is replaced with either: + +@table @env +@item GENERIC +For the bus used in generic cases. That is all but the cases +of the buses listed below. +@item AUDIO +For the bus used in with the audio subsystem is involved. +@item VIDEO +For the bus used in with the video subsystem is involved. +@item INPUT +For the bus used in with the input subsystem is involved. +@item FILES +For the bus used in with the storage subsystem is involved. +@end table + +This list may be extended in the future. Therefore, and for +other conventions, project-private buses should be tracked +using @env{X_BUS}, where @env{X} is the project name. + +Messages broadcasted on a bus cannot be longer than 2047 bytes, +excluding NUL termination. Message should be encoded in UTF-8, +and most not contain the NUL character. + +Broadcasted message should start with the process ID whence +the message originated, followed by a single regular space. +If the process is ephemeral@footnote{The process exits after +the broadcast, or shortly thereafter.}, 0 should be used instead +of the process identifier. + + + +@node Invoking +@chapter Invoking + +@command{bus} includes the following commands: + +@table @command +@item create +Create a bus. +See @ref{bus create} for more information. +@item remove +Remove a bus. +See @ref{bus remove} for more information. +@item listen +Listen for new message on a bus. +See @ref{bus listen} for more information. +@item wait +Listen for one new message only on a bus. +See @ref{bus wait} for more information. +@item broadcast +Broadcast a message on a bus. +See @ref{bus broadcast} for more information. +@item chmod +Change permissions on a bus. +See @ref{bus chmod} for more information. +@item chown +Change ownership of a bus. +See @ref{bus chown} for more information. +@item chgrp +Change group ownership of a bus. +See @ref{bus chgrp} for more information. +@end table + +Upon successful completion, these commands exit with the value +0. On failure, they exit with the value 1. If the command is +not recognised the exit value is 2. + +@menu +* bus create:: Create a bus. +* bus remove:: Remove a bus. +* bus listen:: Listen for new message on a bus. +* bus wait:: Listen for one new message only on a bus. +* bus broadcast:: Broadcast a message on a bus. +* bus chmod:: Change permissions on a bus. +* bus chown:: Change ownership of a bus. +* bus chgrp:: Change group ownership of a bus. +@end menu + + + +@node bus create +@section @command{bus create} + +The syntax for invocation of @command{bus create} is +@example +bus create [-x] [--] [@var{PATHNAME}] +@end example + +The command creates a bus and stores the key to it in the +file @var{PATHNAME}. If @var{PATHNAME} is omitted, a +random pathname in @file{$XDG_RUNTIME_DIR/bus} will be +used and printed to stdout. + +If @option{-x} is used, the command will fail if +the file @var{PATHNAME} already exists. + + + + +@node bus remove +@section @command{bus remove} + +The syntax for invocation of @command{bus remove} is +@example +bus remove [--] @var{PATHNAME} +@end example + +The command removes the bus whose key is stored in +the file @var{PATHNAME}. The file holding the +key is also unlinked. + + + +@node bus listen +@section @command{bus listen} + +The syntax for invocation of @command{bus command} is +@example +bus listen [--] @var{PATHNAME} @var{COMMAND} +@end example + +The command listens for new messages on the bus whose +key is stored in the file @var{PATHNAME}. Once a message +is received, @var{COMMAND} will be spawned with the +environment variable @env{msg} (lowercased) set to the +received message. @sc{POSIX} shell syntax applies to +@var{COMMAND}. + + +@node bus wait +@section @command{bus wait} + +The syntax for invocation of @command{bus wait} is +@example +bus wait [--] @var{PATHNAME} @var{COMMAND} +@end example + +The command listens for a new message on the bus whose +key is stored in the file @var{PATHNAME}. Once a message +is received, the process will stop listening for more +messages and @var{COMMAND} will be spawned with the +environment variable @env{msg} (lowercased) set to the +received message. @sc{POSIX} shell syntax applies to +@var{COMMAND}. + + + +@node bus broadcast +@section @command{bus broadcast} + +The syntax for invocation of @command{bus broadcast} is +@example +bus broadcast [-n] [--] @var{PATHNAME} @var{MESSAGE} +@end example + +The command broadcasts the message @var{MESSAGE} on the +bus whose key is stored in the file @var{PATHNAME}. + + + +@node bus chmod +@section @command{bus chmod} + +The syntax for invocation of @command{bus chmod} is +@example +bus chmod [--] @var{PERMISSIONS} @var{PATHNAME} +@end example + +This command changes who have access to the bus whose key +is stored in the file @var{PATHNAME}. In the permissions, +the owner, the group, and others (not in tgroup) are +represented by the symbols @code{u}@footnote{@code{u} +stands for `user'.}, @code{g}, and @code{o}, respectively. +The permissions string is imagined to have always be +prefixed with an @code{=}. This symbols means that all user +classes list after it, and only those classes, as permission +to use the bus. Similarly the symbols @code{+} and @code{-} +can be used to grant and revoke access, respectively. The +symbols @code{=}, @code{+}, and @code{-} can be mixed, and +are interpreted from left to right. Alternatively the +permissions string can be a octal number, where the owner +is represented by any bit in 700 (100, 200, or 400, or any +combination thereof), the group is represented by any bit +in 70, and others (not in the group) is represented by any +bit in 7. + +The current permission of the bus can be retrieved by +running @command{stat} over the file @var{PATHNAME}. + + + +@node bus chown +@section @command{bus chown} + +The syntax for invocation of @command{bus chown} is +@example +bus chown [--] @var{OWNER}[:@var{GROUP}] @var{PATHNAME} +@end example + +This command changes the owner, that owns the bus whose +key is stored in the file @var{PATHNAME}, to the specified +owner. The owner can be specified either with a numerical +user identifier or with a user name. If a group is +specified, the bus's owner-group will be set to that group, +otherwise the group will remain unchanged (not changed +to the group of the new owner.) The group can be specified +either with a numerical group identifier or with a group +name. + +The current ownership of the bus can be retrieved by +running @command{stat} over the file @var{PATHNAME}. + + + +@node bus chgrp +@section @command{bus chgrp} + +The syntax for invocation of @command{bus chgrp} is +@example +bus chgrp [--] @var{GROUP} @var{PATHNAME} +@end example + +This command changes the group, that owns the bus whose +key is stored in the file @var{PATHNAME}, to the specified +group. The group can be specified either with a numerical +group identifier or with a group name. + +The current ownership of the bus can be retrieved by +running @command{stat} over the file @var{PATHNAME}. + + + +@node Interface +@chapter Interface + +To use @command{libbus} in your C program, include the +header file @file{} and link with the flag +@option{-lbus}. + +With exception to @code{bus_poll} and @code{bus_poll_timed}, +all functions return @code{0} upon successful completion, +and @code{-1} in case of failure. @code{bus_poll} and +@code{bus_poll_timed} return @code{NULL} on failure. +On failure on all functions set @code{errno} to indicate +what went wrong. + +@file{} defines the following functions: + +@table @code +@item int bus_create(const char *file, int flags, char **out_file) +This function creates a bus with the asscoiated pathname +specifed by the value of the parameter @code{file}. If +@code{file} is @code{NULL} a random pathname is selected. +This pathname adheres to the convention set forth by +in @ref{Standard}. + +If @code{file} is not @code{NULL} the function fails if the +file already exists if @code{flags} contains @code{BUS_EXCL}. +Otherwise if @code{file} is not @code{NULL}, the function +does nothing if the file already exists. + +If @code{flags} contains @code{BUS_INTR}, the function fails +if it is interrupted. + +Unless @code{out_file} is NULL, the pathname of the bus +should be stored in a new char array stored in @code{*out_file}. +The caller must free the allocated stored in @code{*out_file}. + +If the processes cannot allocate enough memory to perform +the action, the function sets @code{errno} to @code{ENOMEM} +and fails. It may also fail and set @code{errno} to any of +the errors specified for the system calls @code{open} and +@code{write}. + +@item int bus_unlink(const char *file) +This function removes the bus assoicated with the pathname +stored in the parameter @code{file}. The function also +unlinks the file. + +The function may set @code{errno} to any of the following +values and fail for the specified reasons: +@table @code +@item EINVAL +The bus does not exist. +@item EACCES +Operation permission is denied to the calling process. +@item EPERM +The user does not have permission to remove the bus. +@end table +@noindent +It may also fail and set @code{errno} to any of the errors +specified for the system calls @code{unlink} and @code{open}, +and the functions @code{semget} and @code{shmget}. + +@item int bus_open(bus_t *bus, const char *file, int flags) +This function acquires resources required for the process +to use the bus associated with the filename stored in the +parameter @code{file}. The function also stores the resources +in @code{bus} for use by other @command{bus} functions. + +Values for @code{flags} are constructed by a bitwise +inclusive @sc{or} of flags from the following list. +@table @code +@item BUS_RDONLY +The process will only be using the bus for receiving messages. +@item BUS_WRONLY +The process will only be using the bus for sending messages. +@item BUS_RDWR +The process will use the bus for both receiving and sending +messages. +@end table + +The function may set @code{errno} to any of the following +values and fail for the specified reasons: +@table @code +@item ENOMEM +The process cannot allocate enough memory to perform the +action. +@item EACCES +Operation permission is denied to the calling process. +@item EINVAL +The described bus does not exist. +@end table +@noindent +It may also fail and set @code{errno} to any of the errors +specified for the system call @code{open}. + +@item int bus_close(bus_t *bus) +This function disposes of resources allocated to the +process, as referenced in the parameter @code{bus}. + +The function fails and sets @code{errno} to @code{EINVAL} +if the bus does not exist. + +@item int bus_write(const bus_t *bus, const char *message, int flags) +This function broadcasts a message on the bus whose +information is stored in the parameter @code{bus}. The +message read by the function is stored in the parameter +@code{message}. It may not exceeed 2048 bytes, including +NUL termination. + +The function shall fail, and set @code{errno} to +@code{EAGAIN}, if the call would suspend the process and +@code{flags} contains @code{BUS_NOWAIT}. + +The function may fail and set @code{errno} to any of the +errors specified for the function @code{semop}. + +@item int bus_write_timed(const bus_t *bus, const char *message, const struct timespec *timeout, clockid_t clockid) +This function behaves like @code{bus_write}, except if it +is not able to write the message within the specified time, +it will fail and set @code{errno} to @code{EAGAIN}. The +time is specified as an absolute time using the parameter +@code{timeout}. The behaviour is unspecified if @code{timeout} +is @code{NULL}. @code{timeout} is measured with the clock whose +identifier is specified by the parameter @code{clockid}. This +clock must be a predicitable clock@footnote{There are probably +other, undocumented, seemingly arbitrary restrictions too.}. + +The function may fail and set @code{errno} to any of the +errors specified for the functions @code{semop} and +@code{clock_gettime}. + +@item int bus_read(const bus_t *bus, int (*callback)(const char *message, void *user_data), void *user_data) +This function waits for new message to be sent on the bus +specified in the @code{bus} parameter, as provieded by a +previous call to the function @code{bus_open}. Once a +message is received, the parameter-function @code{callback} +is invoked. The parameter @code{message} in @code{callback} +is the received message, and @code{user_data} in +@code{callback} should be @code{user_data} from @code{bus_read}. +However, once the function [@code{bus_read}] has ensured +that it will receive any message sent on the bus, it shall +invoke the parameter-function @code{callback} with +@code{message} set to @code{NULL}, to notify the process that +it can perform any action that requires that it is listening +on the bus. + +After @code{callback} returns, @code{message} may be override. +Therefore @code{callback} should copy message and start a new +thread that uses the copy of @code{message}. @code{callback} +shall return @code{-1} on failure, @code{0} if the function +[@code{bus_read}] should stop listening, or @code{1} if +the function should continue listening. + +The function may fail and set @code{errno} to any of the +errors specified for the function @code{semop}. + +@item int bus_read_timed(const bus_t *bus, int (*callback)(const char *message, void *user_data), void *user_data, const struct timespec *timeout, clockid_t clockid) +This function behaves like @code{bus_read}, except it will +automatically fail and set @code{errno} to @code{EAGAIN} when +the specified time has passed. The time is specified as an +absolute time using the parameter @code{timeout}. The +behaviour is unspecified if @code{timeout} is @code{NULL}. +@code{timeout} is measured with the clock whose identifier +is specified by the parameter @code{clockid}. This clock +must be a predicitable clock@footnote{There are probably +other, undocumented, seemingly arbitrary restrictions too.}. + +The function may fail and set @code{errno} to any of the +errors specified for the functions @code{semop} and +@code{clock_gettime}. + +@item int bus_poll_start(bus_t *bus) +@itemx int bus_poll_stop(const bus_t *bus) +@itemx const char *bus_poll(bus_t *bus, int flags) +@itemx const char *bus_poll_timed(bus_t *bus, const struct timespec *timeout, clockid_t clockid) +The function @code{bus_poll} waits for a message to be +broadcasted on the bus, and return the message it receives. +The function fails if @code{flags} contains @code{BUS_NOWAIT} +and there is not already a message waiting on the bus. +Received messages shall be copied and parsed, and acted +upon, in a separate thread, and the function @code{bus_poll} +or the function @code{bus_poll_stop} called again as soon +as possible. + +The funcion @code{bus_poll_start} must be called before +@code{bus_poll} is called for the first time. When the +process is done listening on the bus, it must call the +function @code{bus_poll_stop}. + +The function @code{bus_poll_timed} behaves like the function +@code{bus_poll}, except if it is not able to read a message +within the specified time, it will fail and set @code{errno} +to @code{EAGAIN}. The time is specified as an absolute time +using the parameter @code{timeout}. The behaviour is +unspecified if @code{timeout} is @code{NULL}. @code{timeout} +is measured with the clock whose identifier is specified by +the parameter @code{clockid}. This clock must be a predicitable +clock@footnote{There are probably other, undocumented, +seemingly arbitrary restrictions too.}. + +Upon successful completion, the functions @code{bus_poll} and +@code{bus_poll_timed} returns the received message. + +These functions may fail and set @code{errno} to any of the +errors specified for the function @code{semop}. The function +@code{bus_poll_timed} may also set @code{errno} to any of +the errors specified for @code{clock_gettime}. + +@item int bus_chown(const char *file, uid_t owner, gid_t group) +This function changes the owner and the group of the bus, +associated with the file whose pathname is stored in the +parameter @code{file}, to the owner and group specified by +the parameters @code{owner} and @code{group}, respectively. + +The current ownership of a bus can be retrieved by calling +@code{stat} over the pathname of the bus. + +The function may fail and set @code{errno} to any of the +errors specified for the functions @code{bus_open}, +@code{chown}, @code{semget}, @code{shmget}, and @code{shmctl} +as well as any errors specified for the commands +@code{IPC_STAT} and @code{IPC_SET} for the function +@code{semctl}. + +@item int bus_chmod(const char *file, mode_t mode) +This function gives access to the bus associated with the +file whose pathname is stored in the parameter @code{file} +according to the following rules: + +@itemize @bullet{} +@item +If @code{mode} contains any of the bits @code{S_IRWXU} +contains, the owner should be given full access to the +bus. Otherwise the owner should have no access. +@item +If @code{mode} contains any of the bits @code{S_IRWXG} +contains, the group should be given read and write +access to the bus. Otherwise the group should have no +access. +@item +If @code{mode} contains any of the bits @code{S_IRWXO} +contains, users that are neither the owner nor member +of the group should be given read and write access to +the bus. Otherwise they should have no access. +@end itemize + +The current permissions of a bus can be retrieved by calling +@code{stat} over the pathname of the bus. + +The function may fail and set @code{errno} to any of the +errors specified for the functions @code{bus_open}, +@code{chmode}, @code{semget}, @code{shmget}, and @code{shmctl} +as well as any errors specified for the commands +@code{IPC_STAT} and @code{IPC_SET} for the function +@code{semctl}. +@end table + +There is not reason for poking around in @code{bus_t} +(@code{struct bus}). It should be considered opaque. +You can read the documentation in @file{} if +you want to know what is in it. + + + + +@node Protocol +@chapter Protocol + +@command{bus} is built upon following three procedures. + +@noindent +@code{create} +@example +@w{@xrm{}Select a filename.@xtt{}} + +@w{@xrm{}Create XSI semaphore array @{@code{S} = 0, @code{W} = 0, @code{X} = 1, @code{Q} = 0, @code{N} = 0@}@xtt{}} +@w{@xrm{}with random key. Store the semaphore array's key in decimal form@xtt{}} +@w{@xrm{}on the first line in the selected file.@xtt{}} + +@w{@xrm{}Create XSI shared memory, with an allocation of 2048 bytes, with@xtt{}} +@w{@xrm{}a random key. Store the shared memory's key in decimal form on@xtt{}} +@w{@xrm{}the second line in the selected file.@xtt{}} +@end example + +@noindent +@code{broadcast} +@example +with P(X): + Z(W) + @w{@xrm{}Write NUL-terminate message to shared memory@xtt{}} + with V(N): -- (1) + Q := 0 + Z(S) + +-- (1) @w{@xrm{}may be omitted if semaphores are known that@xtt{}} + @w{P()@xrm{}, @xtt{}Z()@xrm{}, @xtt{}V()@xrm{} cannot create a race condition@xtt{}} + @w{@xrm{}with a processes running @xtt{}Z()@xrm{}.@xtt{}} +@end example + +@noindent +@code{listen} +@example +with V(S): + forever: + V(Q) + Z(Q) + @w{@xrm{}Read NUL-terminated message from shared memory@xtt{}} + if breaking: + break + with V(W): + with P(S): + Z(S) + Z(N) +@end example + +@noindent +@code{V(a)} means that semaphore a is released.@* +@code{P(a)} means that semaphore a is acquired.@* +@code{Z(a)} means that the process waits for semaphore a to become 0.@* +@code{with P(a)} that @code{P(a)} is done before the entering the scope, +and @code{V(a)} is done when exiting the scope. It also means that +these actions [@code{P(a)} and @code{V(a)}] are undone when the process +exits, or if the call fails.@* +@code{with V(a)} is to @code{V(a)} as @code{with P(a)} is to @code{P(a)}. + + + +@node Rationale +@chapter Rationale + +We need an interprocess communication system similar +to message queues. But we need broadcasting rather than +anycasting, so we have a fast, simple and daemonless +system for announcing events to any processes that +might be interested. + + + +@node Examples +@chapter Examples + +This chapter contains usecase examples and +API-demonstrations. You will find that they are (on +a standard installation) installed on your system. + +@menu +* Audio-volume control:: +* Telephony and music:: +* Timed:: +* Nonblocking:: +* Daemon-dependencies:: +@end menu + + + + +@node Audio-volume control +@section Audio-volume control + +Assume you have program that display the audio volume. +This program checks every second third if the volume +have changed. + +Also assume that you use @command{amixer} to change the +volume, most often by using keybindings via @command{xbindkeys}. + +To reduce the delay, you want to send a signal to the +monitor program that the volume have changed. For this +more primitive IPC is sufficient, but lets assume there +are other programs interested in this information too. + +To accomplish this, you create a wrapper for @command{amixer} +that broadcasts updates on a bus. This wrapper is +installed as @command{~/.local/bin/amixer}, and +@command{~/.local/bin/} is included in @env{$PATH} +before @command{/usr/bin}. +@* + +@noindent +Before starting run @command{./init}, this code is +should be run from your profile file if you want to +implement this on your system. After running +@command{./init}, you can start one or more listeners +by running @command{./alsa-monitor}. + +To change the volume run +@code{./amixer -c 0 -- set Master 5%+} or similar. + +When you are done run @command{./cleanup}. + + +@subsubheading @file{./amixer} +@example +#!/bin/sh +/usr/bin/amixer "$@@" +for arg in "$@@"; do + if [ "$@{arg@}" = "set" ] || \ + [ "$@{arg@}" = "sset" ] || \ + [ "$@{arg@}" = "cset" ]; then + exec bus broadcast "/tmp/example-bus" '0 volume-changed *' + fi +done +@end example + + +@subsubheading @file{./cleanup} +@example +#!/bin/sh +exec bus remove "/tmp/example-bus" +@end example + + +@subsubheading @file{./init} +@example +#!/bin/sh +bus create "/tmp/example-bus" + +# @w{@xrm{}The following code is more suitable in the real world,@xtt{}} +# @w{@xrm{}if used, the other files should use @xtt{}"$@{BUS_AUDIO@}"} +# @w{@xrm{}instead of @xtt{}"/tmp/example-bus"@xrm{}.@xtt{}} +# +# export BUS_AUDIO="$@{XDG_RUNTIME_DIR@}/bus/audio" +# if [ ! -f "$@{BUS_AUDIO@}" ]; then +# bus create "$@{BUS_AUDIO@}" +# fi +@end example + + +@subsubheading @file{./monitor} +@example +#!/bin/sh +if [ $# = 1 ]; then + if [ "$(echo "$@{1@}" | cut -d ' ' -f 2)" = "volume-changed" ]; then + printf '\e[H\e[2J' + amixer get Master + fi + exit 0 +fi + +exec 2>/dev/null + +printf '\e[?1049h\e[H\e[2J' +trap -- "printf '\e[?1049l'" SIGINT +bus listen "/tmp/example-bus" \'"$@{0/\'/\'\\\'\'@}"\'' "$@{msg@}"' +@end example + + + +@node Telephony and music +@section Telephony and music + +Assume you have a music player and a telephony program. +You might like it if the music player pauses whenever +you make or receive a call. You may also like it, if +the music resumed when the call ended. + +In this example we will assume you the have @command{mocp} +(@command{moc} package) running. And we will use the shell to +simulate a telephony program. +@* + +@noindent +First of, run make to build this example. Before +starting run @command{./init}. And when you are +done run @command{./cleanup}. + +In one terminal run @command{./monitor}. This program +will pause @command{mocp} when you make or receive a call, +it will also resume @command{mocp} when all calls have +ended if it did pause @command{mocp}. + +Then start any positive number of terminals. +We will pretend that each of them are telephony +programs. To make or receive a call, run +@command{./receive-or-make-call}, when you want to +end the pretend call, run @command{./end-call} from +the terminal (or more accurately, from the same +process.) + + +@subsubheading @file{./Makefile} +@example +COMMANDS = init cleanup monitor end-call receive-or-make-call + +all: $@{COMMANDS@} +%: %.c + $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< +clean: + -rm $@{COMMANDS@} + +.PHONY: all clean +@end example + + +@subsubheading @file{./cleanup.c} +@example +#include +#include + +int main() +@{ + return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); +@} +@end example + + +@subsubheading @file{./end-call.c} +@example +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static char message[BUS_MEMORY_SIZE]; + +int main() +@{ + bus_t bus; + sprintf(message, "%ji unforce-pause", (intmax_t)getppid()); + /* @w{@xrm{}Yes, PPID; in this example we pretend the shell is the telephony process.@xtt{}} */ + t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); + t (bus_write(&bus, message, 0)); + bus_close(&bus); + return 0; + +fail: + perror("end-call"); + bus_close(&bus); + return 1; +@} +@end example + + +@subsubheading @file{./init.c} +@example +#include +#include + +int main() +@{ + return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); +@} +@end example + + +@subsubheading @file{./monitor.c} +@example +#include +#include +#include +#include + +static size_t pauser_count = 0; +static size_t pausers_size = 0; +static char* pausers = NULL; + +static int is_moc_playing(void) +@{ + return !WEXITSTATUS(system("env LANG=C mocp -i 2>/dev/null |" + "grep 'State: PLAY' >/dev/null")); +@} + +/* @w{@xrm{}In a proper implementation, message whould be copyied, and then@xtt{}} + * @w{@xrm{}a new thread would be created that parsed the copy. But that is@xtt{}} + * @w{@xrm{}too much for an example, especially since it would also require@xtt{}} + * @w{@xrm{}a mutex to make sure two threads do not modify data at the same@xtt{}} + * @w{@xrm{}time, causing chaos.@xtt{}} */ +static int callback(const char *message, void *user_data) +@{ + char *msg = NULL; + size_t len = 0; + if (message == 0) + return 1; + while ((len < 2047) && message[len]) + len++; + msg = malloc((len + 1) * sizeof(char)); + t (msg == NULL); + memcpy(msg, message, len * sizeof(char)); + msg[len] = 0; + /* @w{@xrm{}BEGIN run as in a separate thread@xtt{}} */ + if (pauser_count || is_moc_playing()) @{ + char *begin = strchr(msg, ' '); + ssize_t pid; + int requests_pause; + if (begin == NULL) + goto done; + *begin++ = 0; + pid = (ssize_t)atoll(msg); + if (pid < 1) /* @w{@xrm{}We need a real PID, too bad there is@xtt{}} + * @w{@xrm{}no convient way to detect if it dies.@xtt{}} */ + goto done; + if ((strstr(begin, "force-pause ") == begin) || + !strcmp(begin, "force-pause")) + requests_pause = 1; + else if ((strstr(begin, "unforce-pause ") == begin) || + !strcmp(begin, "unforce-pause")) + requests_pause = 0; + else + goto done; + if ((size_t)pid >= pausers_size) @{ + pausers = realloc(pausers, (size_t)(pid + 1) * sizeof(char)); + t (pausers == NULL); /* @w{@xrm{}Let's ignore the memory leak.@xtt{}} */ + memset(pausers + pausers_size, 0, + ((size_t)(pid + 1) - pausers_size) * sizeof(char)); + pausers_size = (size_t)(pid + 1); + @} + if (pausers[pid] ^ requests_pause) @{ + pauser_count += requests_pause ? 1 : -1; + pausers[pid] = requests_pause; + if (pauser_count == (size_t)requests_pause) + system(requests_pause ? "mocp -P" : "mocp -U"); + @} + @} + /* @w{@xrm{}END run as in a separate thread@xtt{}} */ + goto done; + (void) user_data; + +fail: + perror("monitor"); + return -1; + +done: + free(msg); + return 1; +@} + +int main() +@{ + bus_t bus; + t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); + t (bus_read(&bus, callback, NULL)); + bus_close(&bus); + free(pausers); + return 0; + +fail: + perror("monitor"); + bus_close(&bus); + free(pausers); + return 1; +@} +@end example + + +@subsubheading @file{./receive-or-make-call.c} +@example +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static char message[BUS_MEMORY_SIZE]; + +int main() +@{ + bus_t bus; + sprintf(message, "%ji force-pause", (intmax_t)getppid()); + /* @w{@xrm{}Yes, PPID; in this example we pretend the shell is the telephony process.@xtt{}} */ + t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); + t (bus_write(&bus, message, 0)); + bus_close(&bus); + return 0; + +fail: + perror("receive-or-make-call"); + bus_close(&bus); + return 1; +@} +@end example + + + +@node Timed +@section Timed + +This example shows how to use timed operations. + +First of, run make to build this example. + +To start the example run @command{./init}. When you are +done run @command{./cleanup}. + +Running instances of @command{./poll} will wait for new +messages continuously, but with one second timeouts. + +@command{./slow-poll} works like @command{./poll}, except +it will sleep for one second every time it receives +a message. + +Running instances of @command{./read} will read for ten +seconds and then time out. + +@command{./poll}, @command{./read}, and @command{./slow-poll} +will stop if the message "stop" is broadcasted. + +@command{./write} will wait for atmost a tenth of a +seconds before failing. This means that if two instances +of @command{./write} is started at the same time one of +them will fail if @command{./slow-poll} is running. + +@command{./poll}, @command{./read}, @command{./init} and +@command{./cleanup} are run without any additional +arguments. @command{./write} is run with the message +as the second argument. + + +@subsubheading @file{./Makefile} +@example +COMMANDS = init cleanup write poll read slow-poll + +all: $@{COMMANDS@} +%: %.c + $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< +clean: + -rm $@{COMMANDS@} + +.PHONY: all clean +@end example + + +@subsubheading @file{./cleanup.c} +@example +#include +#include + +int main() +@{ + return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); +@} +@end example + + +@subsubheading @file{./init.c} +@example +#include +#include + +int main() +@{ + return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); +@} +@end example + + +@subsubheading @file{./poll.c} +@example +#include +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +int main() +@{ + bus_t bus; + const char *message; + long long tick = 0; + struct timespec timeout; + t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); + t (bus_poll_start(&bus)); + for (;;) @{ + t (clock_gettime(CLOCK_MONOTONIC, &timeout)); + timeout.tv_sec += 1; + message = bus_poll_timed(&bus, &timeout, CLOCK_MONOTONIC); + if (message == NULL) @{ + t (errno != EAGAIN); + printf("waiting... %lli\n", ++tick); + continue; + @} + tick = 0; + message = strchr(message, ' ') + 1; + if (!strcmp(message, "stop")) + break; + printf("\033[01m%s\033[21m\n", message); + @} + t (bus_poll_stop(&bus)); + bus_close(&bus); + return 0; + +fail: + perror("poll"); + bus_poll_stop(&bus); + bus_close(&bus); + return 1; +@} +@end example + + +@subsubheading @file{./read.c} +@example +#include +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static int callback(const char *message, void *user_data) +@{ + (void) user_data; + + if (message == NULL) + return 1; + + message = strchr(message, ' ') + 1; + if (!strcmp(message, "stop")) + return 0; + printf("%s\n", message); + return 1; +@} + +int main() +@{ + bus_t bus; + struct timespec timeout; + t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); + t (clock_gettime(CLOCK_MONOTONIC, &timeout)); + timeout.tv_sec += 10; + t (bus_read_timed(&bus, callback, NULL, &timeout, CLOCK_MONOTONIC)); + bus_close(&bus); + return 0; + +fail: + perror("poll"); + bus_poll_stop(&bus); + bus_close(&bus); + return 1; +@} +@end example + + +@subsubheading @file{./slow-poll.c} +@example +#include +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +int main() +@{ + bus_t bus; + const char *message; + long long tick = 0; + struct timespec timeout; + t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); + t (bus_poll_start(&bus)); + for (;;) @{ + t (clock_gettime(CLOCK_MONOTONIC, &timeout)); + timeout.tv_sec += 1; + message = bus_poll_timed(&bus, &timeout, CLOCK_MONOTONIC); + if (message == NULL) @{ + t (errno != EAGAIN); + printf("waiting... %lli\n", ++tick); + continue; + @} + tick = 0; + message = strchr(message, ' ') + 1; + if (!strcmp(message, "stop")) + break; + printf("\033[01m%s\033[21m\n", message); + sleep(1); + @} + t (bus_poll_stop(&bus)); + bus_close(&bus); + return 0; + +fail: + perror("poll"); + bus_poll_stop(&bus); + bus_close(&bus); + return 1; +@} +@end example + + +@subsubheading @file{./write.c} +@example +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static char message[BUS_MEMORY_SIZE]; + +int main(int argc, char *argv[]) +@{ + bus_t bus; + struct timespec timeout; + if (argc < 2) @{ + fprintf(stderr, "%s: USAGE: %s message\n", argv[0], argv[0]); + return 2; + @} + sprintf(message, "0 %s", argv[1]); + t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); + t (clock_gettime(CLOCK_MONOTONIC, &timeout)); + timeout.tv_nsec += 100000000L; + t (bus_write_timed(&bus, message, &timeout, CLOCK_MONOTONIC)); + bus_close(&bus); + return 0; + +fail: + perror("write"); + bus_close(&bus); + return 1; +@} +@end example + + + +@node Nonblocking +@section Nonblocking + +This example shows how to use bus_poll instead of bus_read, +and how to do non-blocking polling and non-blocking writing. + +First of, run make to build this example. + +To start the example run @command{./init}. When you are done +run @command{./cleanup}. + +Running instances of @command{./poll} will check every second +if there is a new inbound message. Between these checks +@command{./write} will wait for all @command{./poll}:s to +receive the message. This means that @command{./write} blocks +while @command{./poll} sleeps. If two or more instances of +@command{./write} is started at approximately the same time, +only one will continue to write a message on the bus, the +others will fail. + +@command{./poll} will stop if the message ``stop'' is +broadcasted. + +@command{./poll}, @command{./init} and @command{./cleanup} +are run without any additional arguments. @command{./write} +is run with the message as the second argument. + + +@subsubheading @file{./Makefile} +@example +COMMANDS = init cleanup write poll + +all: $@{COMMANDS@} +%: %.c + $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< +clean: + -rm $@{COMMANDS@} + +.PHONY: all clean +@end example + + +@subsubheading @file{./cleanup.c} +@example +#include +#include + +int main() +@{ + return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); +@} +@end example + + +@subsubheading @file{./init.c} +@example +#include +#include + +int main() +@{ + return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); +@} +@end example + + +@subsubheading @file{./poll.c} +@example +#include +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +int main() +@{ + bus_t bus; + const char *message; + long long tick = 0; + t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); + t (bus_poll_start(&bus)); + for (;;) @{ + message = bus_poll(&bus, BUS_NOWAIT); + if (message == NULL) @{ + t (errno != EAGAIN); + printf("waiting... %lli\n", ++tick); + sleep(1); + continue; + @} + tick = 0; + message = strchr(message, ' ') + 1; + if (!strcmp(message, "stop")) + break; + printf("\033[01m%s\033[21m\n", message); + @} + t (bus_poll_stop(&bus)); + bus_close(&bus); + return 0; + +fail: + perror("poll"); + bus_poll_stop(&bus); + bus_close(&bus); + return 1; +@} +@end example + + +@subsubheading @file{./write.c} +@example +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static char message[BUS_MEMORY_SIZE]; + +int main(int argc, char *argv[]) +@{ + bus_t bus; + if (argc < 2) @{ + fprintf(stderr, "%s: USAGE: %s message\n", argv[0], argv[0]); + return 2; + @} + sprintf(message, "0 %s", argv[1]); + t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); + t (bus_write(&bus, message, BUS_NOWAIT)); + bus_close(&bus); + return 0; + +fail: + perror("write"); + bus_close(&bus); + return 1; +@} +@end example + + + +@node Daemon-dependencies +@section Daemon-dependencies + +This example shows how bus can be used in an init system +to provide ``aggressivly'' parallel startup of daemons. + +First of, run make to build this example. + +To start the example run @command{./init}. It will print in +red export-statement you may want to run i other terminals. +You will need to select at least one daemon, for example +you can run @code{./init d-ntp}. The available pretend +daemons are: @command{d-network}, @command{d-ntp}, and +@command{d-ssh}. + +When you are done run @command{./cleanup} with @env{BUS_INIT} +exported with the value printed by @command{./init}. + + +@subsubheading @file{./Makefile} +@example +COMMANDS = announce await-ready await-started cleanup \ + init require start-daemon test-daemon + +all: $@{COMMANDS@} +%: %.c + $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< +clean: + -rm $@{COMMANDS@} + -rm -r run + +.PHONY: all clean +@end example + + +@subsubheading @file{./announce.c} +@example +#include +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static char arg[4098]; + +int main(int argc, char *argv[]) +@{ + bus_t bus; + if (argc < 3) + return fprintf(stderr, "USAGE: %s state daemon", *argv), 2; + t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); + sprintf(arg, "%ji %s %s", (intmax_t)getppid(), argv[1], argv[2]); + t (bus_write(&bus, arg, 0)); + t (bus_close(&bus)); + return 0; + +fail: + perror("announce"); + return 1; +@} +@end example + + +@subsubheading @file{./await-ready.c} +@example +#include +#include +#include +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static char arg[4098]; +static int argc; +static char **argv; +static int remaining = 0; +static char *started = NULL; +static char msg[BUS_MEMORY_SIZE]; + +static void announce_wait(pid_t pid) +@{ + bus_t bus; + int i; + t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); + for (i = 1; i < argc; i++) @{ + if (!started[i]) @{ + sprintf(arg, "%ji awaiting-ready %s", (intmax_t)pid, argv[i]); + t (bus_write(&bus, arg, 0)); + @} + @} + t (bus_close(&bus)); + return; + +fail: + perror("await-ready"); +@} + +static int callback(const char *message, void *user_data) +@{ + int i; + char *arg2; + char *arg3; + pid_t pid; + pid_t ppid; + + if (!message) @{ + ppid = getppid(); + pid = fork(); + if (pid == 0) @{ + if (fork() == 0) + announce_wait(ppid); + exit(0); + @} else @{ + (void) waitpid(pid, NULL, 0); + /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ + @} + return 1; + @} + + strncpy(msg, message, BUS_MEMORY_SIZE - 1); + msg[BUS_MEMORY_SIZE - 1] = 0; + + arg2 = strchr(msg, ' '); + if (!arg2) + return 1; + arg3 = strchr(++arg2, ' '); + if (!arg3) + return 1; + *arg3++ = 0; + + if (strcmp(arg2, "ready")) + return 1; + + for (i = 1; i < argc; i++) + if (!started[i] && !strcmp(argv[i], arg3)) + started[i] = 1, remaining--; + + return !!remaining; + (void) user_data; +@} + +int main(int argc_, char *argv_[]) +@{ + bus_t bus; + int i; + + argc = argc_; + argv = argv_; + + if (argc < 2) + return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; + t (bus_open(&bus, getenv("BUS_INIT"), BUS_RDONLY)); + started = calloc(argc, sizeof(char)); + t (started == NULL); + + started[0] = 1; + for (i = 1; i < argc; i++) @{ + sprintf(arg, "grep '^%s$'" + " <\"$@{XDG_RUNTIME_DIR@}/ready-daemons\"" + " >/dev/null", + argv[i]); + if (!WEXITSTATUS(system(arg))) + started[i] = 1; + else + remaining++; + @} + + if (remaining) + bus_read(&bus, callback, NULL); + + bus_close(&bus); + free(started); + return 0; + +fail: + perror("await-ready"); + bus_close(&bus); + free(started); + return 1; +@} +@end example + + +@subsubheading @file{./await-started.c} +@example +#include +#include +#include +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static char arg[4098]; +static int argc; +static char **argv; +static int remaining = 0; +static char *started = NULL; +static char msg[BUS_MEMORY_SIZE]; + +static void announce_wait(pid_t pid) +@{ + bus_t bus; + int i; + t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); + for (i = 1; i < argc; i++) @{ + if (!started[i]) @{ + sprintf(arg, "%ji awaiting-started %s", (intmax_t)pid, argv[i]); + t (bus_write(&bus, arg, 0)); + @} + @} + t (bus_close(&bus)); + return; + +fail: + perror("await-started"); +@} + +static int callback(const char *message, void *user_data) +@{ + int i; + char *arg2; + char *arg3; + pid_t pid; + pid_t ppid; + + if (!message) @{ + ppid = getppid(); + pid = fork(); + if (pid == 0) @{ + if (fork() == 0) + announce_wait(ppid); + exit(0); + @} else @{ + (void) waitpid(pid, NULL, 0); + /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ + @} + return 1; + @} + + strncpy(msg, message, BUS_MEMORY_SIZE - 1); + msg[BUS_MEMORY_SIZE - 1] = 0; + + arg2 = strchr(msg, ' '); + if (!arg2) + return 1; + arg3 = strchr(++arg2, ' '); + if (!arg3) + return 1; + *arg3++ = 0; + + if (strcmp(arg2, "started") && strcmp(arg2, "ready")) + return 1; + + for (i = 1; i < argc; i++) + if (!started[i] && !strcmp(argv[i], arg3)) + started[i] = 1, remaining--; + + return !!remaining; + (void) user_data; +@} + +int main(int argc_, char *argv_[]) +@{ + bus_t bus; + int i; + + argc = argc_; + argv = argv_; + + if (argc < 2) + return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; + t (bus_open(&bus, getenv("BUS_INIT"), BUS_RDONLY)); + started = calloc(argc, sizeof(char)); + t (started == NULL); + + started[0] = 1; + for (i = 1; i < argc; i++) @{ + sprintf(arg, "grep '^%s$'" + " <\"$@{XDG_RUNTIME_DIR@}/started-daemons\"" + " >/dev/null", + argv[i]); + if (!WEXITSTATUS(system(arg))) @{ + started[i] = 1; + @} else @{ + sprintf(arg, "grep '^%s$'" + " <\"$@{XDG_RUNTIME_DIR@}/ready-daemons\"" + " >/dev/null", + argv[i]); + if (!WEXITSTATUS(system(arg))) + started[i] = 1; + else + remaining++; + @} + @} + + if (remaining) + bus_read(&bus, callback, NULL); + + bus_close(&bus); + free(started); + return 0; + +fail: + perror("await-started"); + bus_close(&bus); + free(started); + return 1; +@} +@end example + + +@subsubheading @file{./cleanup.c} +@example +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +int main() +@{ + char *bus_address = getenv("BUS_INIT"); + if (!bus_address || !*bus_address) @{ + fprintf(stderr, "$BUS_INIT has not been set, its export statement " + "should have been printed in bold red by ./init\n"); + return 1; + @} + t (bus_unlink(bus_address)); + return 0; + +fail: + perror("cleanup"); + return 1; +@} +@end example + + +@subsubheading @file{./d-network} +@example +#!/bin/sh +PATH=.:$PATH +d=d-network + +echo $d: starting +sleep 2 +echo $d: ready +announce ready $d +@end example + + +@subsubheading @file{./d-ntp} +@example +#!/bin/sh +PATH=.:$PATH +d=d-ntp + +require d-network +echo $d: started +announce started $d +await-ready d-network +echo $d: ready +announce ready $d +@end example + + +@subsubheading @file{./d-ssh} +@example +#!/bin/sh +PATH=.:$PATH +d=d-ssh + +require d-network +echo $d: starting +sleep 1 +echo $d: started +announce started $d +sleep 1 +echo $d: ready +announce ready $d +@end example + + +@subsubheading @file{./init.c} +@example +#include +#include +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail +#define _2(...) __VA_ARGS__, __VA_ARGS__ + +static char msg[BUS_MEMORY_SIZE]; +static int argc; +static char **argv; +static char arg[4098]; + +static void start_daemons() +@{ + int i; + for (i = 1; i < argc; i++) + if (fork() == 0) + execl("./start-daemon", "./start-daemon", argv[i], NULL); +@} + +static int callback(const char *message, void *user_data) +@{ + pid_t pid; + char *arg2; + char *arg3; + if (!message) @{ + pid = fork(); + t (pid == -1); + if (pid == 0) @{ + if (fork() == 0) @{ + start_daemons(); + @} + exit(0); + @} else @{ + (void) waitpid(pid, NULL, 0); + /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ + @} + return 1; + @} + + strncpy(msg, message, BUS_MEMORY_SIZE - 1); + msg[BUS_MEMORY_SIZE - 1] = 0; + + pid = fork(); + t (pid == -1); + + if (pid == 0) @{ + if (fork() == 0) @{ + arg2 = strchr(msg, ' '); + if (arg2 == NULL) + exit(0); + arg3 = strchr(++arg2, ' '); + if (arg3 == NULL) + exit(0); + *arg3++ = 0; + if (!strcmp(arg2, "require")) @{ + execl(_2("./start-daemon"), arg3, NULL); + @} else if (!strcmp(arg2, "awaiting-started")) @{ + execl(_2("./test-daemon"), arg3, "started", NULL); + @} else if (!strcmp(arg2, "awaiting-ready") || + !strcmp(arg2, "awaiting")) @{ + execl(_2("./test-daemon"), arg3, "ready", NULL); + @} else if (!strcmp(arg2, "started")) @{ + sprintf(arg, + "grep '^%s\\$' < \"%s\" >/dev/null || echo %s >> \"%s\"", + _2(arg3, "$@{XDG_RUNTIME_DIR@}/started-daemons")); + execlp(_2("sh"), "-c", arg, NULL); + @} else if (!strcmp(arg2, "ready")) @{ + sprintf(arg, + "grep '^%s\\$' < \"%s\" >/dev/null || echo %s >> \"%s\"", + _2(arg3, "$@{XDG_RUNTIME_DIR@}/ready-daemons")); + execlp(_2("sh"), "-c", arg, NULL); + @} + @} + exit(0); + @} else @{ + (void) waitpid(pid, NULL, 0); + /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ + @} + + return 1; + (void) user_data; + +fail: + perror("init"); + return -1; +@} + +int main(int argc_, char *argv_[]) +@{ + char *bus_address = NULL; + bus_t bus; + argv = argv_; + argc = argc_; + if (argc < 2) @{ + fprintf(stderr, "USAGE: %s daemon...\n", *argv); + return 1; + @} + t (setenv("XDG_RUNTIME_DIR", "./run", 1)); + /* @w{@xrm{}Real init systems with not have the period.@xtt{}} */ + system("mkdir -p -- \"$@{XDG_RUNTIME_DIR@}\""); + system("truncate -s 0 -- \"$@{XDG_RUNTIME_DIR@}/started-daemons\""); + system("truncate -s 0 -- \"$@{XDG_RUNTIME_DIR@}/ready-daemons\""); + t (bus_create(NULL, 1, &bus_address)); + fprintf(stderr, "\033[00;01;31mexport BUS_INIT=%s\033[00m\n", + bus_address); + fprintf(stderr, "\033[00;31mexport XDG_RUNTIME_DIR=./run\033[00m\n"); + t (setenv("BUS_INIT", bus_address, 1)); + t (bus_open(&bus, bus_address, BUS_RDONLY)); + t (bus_read(&bus, callback, NULL)); + bus_close(&bus); + free(bus_address); + return 0; + +fail: + perror("init"); + bus_close(&bus); + free(bus_address); + return 1; +@} +@end example + + +@subsubheading @file{./require.c} +@example +#include +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static char arg[4098]; + +int main(int argc, char *argv[]) +@{ + bus_t bus; + int i; + if (argc < 2) + return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; + t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); + + for (i = 1; i < argc; i++) @{ + sprintf(arg, "grep '^%s$' <\"%s\" >/dev/null", + argv[i], "$@{XDG_RUNTIME_DIR@}/started-daemons"); + if (WEXITSTATUS(system(arg))) @{ + sprintf(arg, "%ji require %s", (intmax_t)getppid(), argv[i]); + t (bus_write(&bus, arg, 0)); + @} + @} + + bus_close(&bus); + return 0; + +fail: + perror("require"); + bus_close(&bus); + return 1; +@} +@end example + + +@subsubheading @file{./start-daemon.c} +@example +#include +#include +#include +#include +#include +#include + +static char arg[4098]; + +int main(int argc, char *argv[]) +@{ + if (argc != 2) @{ + fprintf(stderr, "This program should be called from ./init\n"); + return 2; + @} + + sprintf(arg, "grep '^%s$' <\"%s\" >/dev/null", + argv[1], "$@{XDG_RUNTIME_DIR@}/started-daemons"); + if (!WEXITSTATUS(system(arg))) + return 0; + sprintf(arg, "grep '^%s$' <\"%s\" >/dev/null", + argv[1], "$@{XDG_RUNTIME_DIR@}/ready-daemons"); + if (!WEXITSTATUS(system(arg))) + return 0; + + sprintf(arg, "./%s", argv[1]); + execlp(arg, arg, NULL); + perror("start-daemon"); + return 1; +@} +@end example + + +@subsubheading @file{./test-daemon.c} +@example +#include +#include +#include +#include + +#define t(stmt) if (stmt) goto fail + +static char arg[4098]; + +int main(int argc, char *argv[]) +@{ + bus_t bus; + if (argc != 3) @{ + fprintf(stderr, "This program should be called from ./init\n"); + return 2; + @} + +retry: + sprintf(arg, "grep '^%s$'" + " <\"$@{XDG_RUNTIME_DIR@}/%s-daemons\"" + " >/dev/null", + argv[1], argv[2]); + if (!WEXITSTATUS(system(arg))) @{ + t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); + sprintf(arg, "0 %s %s", argv[2], argv[1]); + t (bus_write(&bus, arg, 0)); + bus_close(&bus); + @} else if (!strcmp(argv[2], "started")) @{ + argv[2] = "ready"; + goto retry; + @} + return 0; + +fail: + perror("test-daemon"); + return 1; +@} +@end example + + + +@node GNU Free Documentation License +@appendix GNU Free Documentation License +@include fdl.texinfo + +@bye + diff --git a/bus_chmod.3 b/bus_chmod.3 new file mode 100644 index 0000000..ab8cb14 --- /dev/null +++ b/bus_chmod.3 @@ -0,0 +1,56 @@ +.TH BUS_CHMOD 3 BUS +.SH NAME +bus_chmod - Change bus mode bits +.SH SYNOPSIS +.LP +.nf +#include +.P +int bus_chmod(const char *\fIfile\fP, mode_t \fImode\fP); +.fi +.SH DESCRIPTION +The +.BR bus_chmod () +function gives access to the bus associated with \fIfile\fP +according to the following rules: +.TP +* +If (\fImode\fP &S_IRWXU) the owner should be given full access to the +bus. Otherwise the owner should have no access. +.TP +* +If (\fImode\fP &S_IRWXG) the group should be given read and write +access to the bus. Otherwise the group should have no access. +.TP +* +If (\fImode\fP &S_IRWXO) others (except the group) should be given +read and write access to the bus. Otherwise others should have no +access. +.PP +The current permissions of a bus can be retrieved by running +.BR stat (3) +over the \fIpathname\fP of the bus. +.SH RETURN VALUES +Upon successful completion, the function returns 0. Otherwise the +function returns -1 and sets \fIerrno\fP to indicate the error. +.SH ERRORS +The +.BR bus_chown (3) +function may fail and set \fIerrno\fP to any of the +errors specified for +.BR bus_open (3), +.BR chmod (3), +.BR semget (3), +.BR shmget (3) +and +.BR shmctl (3) +as well as any errors specified for the \fIIPC_STAT\fP and +\fIIPC_SET\fP commands for +.BR semctl (3). +.SH SEE ALSO +.BR bus-create (1), +.BR bus (5), +.BR libbus (7), +.BR bus_open (3), +.BR bus_read (3), +.BR stat (3) diff --git a/bus_chown.3 b/bus_chown.3 new file mode 100644 index 0000000..d90b131 --- /dev/null +++ b/bus_chown.3 @@ -0,0 +1,43 @@ +.TH BUS_CHOWN 3 BUS +.SH NAME +bus_chown - Change bus owner and group +.SH SYNOPSIS +.LP +.nf +#include +.P +int bus_chown(const char *\fIfile\fP, uid_t \fIowner\fP, gid_t \fIgroup\fP); +.fi +.SH DESCRIPTION +The +.BR bus_chown () +function changes the owner and the group of the bus associated with +\fIfile\fP to the \fIowner\fP and \fIgroup\fP, respectively. +.PP +The current ownership of a bus can be retrieved by running +.BR stat (3) +over the \fIpathname\fP of the bus. +.SH RETURN VALUES +Upon successful completion, the function returns 0. Otherwise the +function returns -1 and sets \fIerrno\fP to indicate the error. +.SH ERRORS +The +.BR bus_chown (3) +function may fail and set \fIerrno\fP to any of the +errors specified for +.BR bus_open (3), +.BR chown (3), +.BR semget (3), +.BR shmget (3) +and +.BR shmctl (3) +as well as any errors specified for the \fIIPC_STAT\fP and +\fIIPC_SET\fP commands for +.BR semctl (3). +.SH SEE ALSO +.BR bus-create (1), +.BR bus (5), +.BR libbus (7), +.BR bus_open (3), +.BR bus_read (3), +.BR stat (3) diff --git a/bus_close.3 b/bus_close.3 new file mode 100644 index 0000000..13fe22a --- /dev/null +++ b/bus_close.3 @@ -0,0 +1,28 @@ +.TH BUS_CLOSE 3 BUS +.SH NAME +bus_close - Close a bus +.SH SYNOPSIS +.LP +.nf +#include +.P +int bus_close(bus_t *\fIbus\fP); +.fi +.SH DESCRIPTION +The +.BR bus_close () +function disposes of resources allocated to the process, as referenced +in the \fIbus\fP argument. +.SH RETURN VALUES +Upon successful completion, the function returns 0. Otherwise the +function returns -1 and sets \fIerrno\fP to indicate the error. +.SH ERRORS +.TP +.B EINVAL +The bus does not exist. +.SH SEE ALSO +.BR bus-create (1), +.BR bus (5), +.BR libbus (7), +.BR bus_open (3), +.BR bus_unlink (3) diff --git a/bus_create.3 b/bus_create.3 new file mode 100644 index 0000000..8552b95 --- /dev/null +++ b/bus_create.3 @@ -0,0 +1,55 @@ +.TH BUS_CREATE 3 BUS +.SH NAME +bus_create - Create a new bus +.SH SYNOPSIS +.LP +.nf +#include +.P +int bus_create(const char *\fIfile\fP, int \fIflags\fP, char **\fIout_file\fP); +.fi +.SH DESCRIPTION +The +.BR bus_create () +function creates a bus with the asscoiated pathname specifed by the +value of the parameter \fIfile\fP. If \fIfile\fP is \fINULL\fP a random +pathname is selected. This pathname adheres to the convention set forth +by +.BR bus (5). +.PP +If \fIfile\fP is not \fINULL\fP the +.BR bus_create () +function fails if the file already exists if \fIflags\fP contains +\fIBUS_EXCL\fP. Otherwise if \fIfile\fP is not \fINULL\fP, the +.BR bus_create () +function does nothing if the file already exists. +.PP +If \fIflags\fP contains \fIBUS_INTR\fP, the function fails if it is +interrupted. +.PP +Unless \fIout_file\fP is \fINULL\fP, the pathname of the bus should be +stored in a new char array stored in \fI*out_file\fP. The caller must +free the allocated stored in \fI*out_file\fP. +.SH RETURN VALUES +Upon successful completion, the function returns 0. Otherwise the +function return -1 with \fIerrno\fP set to indicate the error. +.SH ERRORS +.TP +.B ENOMEM +The process cannot allocate more memory. +.PP +The +.BR bus_create (3) +function may also fail and set \fIerrno\fP to any +of the errors specified for the routines +.BR open (2) +and +.BR write (2). +.SH SEE ALSO +.BR bus-create (1), +.BR bus (5), +.BR libbus (7), +.BR bus_unlink (3), +.BR bus_open (3), +.BR open (2), +.BR write (2) diff --git a/bus_open.3 b/bus_open.3 new file mode 100644 index 0000000..35abb27 --- /dev/null +++ b/bus_open.3 @@ -0,0 +1,59 @@ +.TH BUS_OPEN 3 BUS +.SH NAME +bus_open - Open a bus +.SH SYNOPSIS +.LP +.nf +#include +.P +int bus_open(bus_t *\fIbus\fP, const char *\fIfile\fP, int \fIflags\fP); +.fi +.SH DESCRIPTION +The +.BR bus_open () +function acquires resources required for the process to use the bus +associated with the filename stored in \fIfile\fP. The function also +stores the resources in \fIbus\fP for use by other +.BR bus +functions. +.PP +Values for \fIflags\fP are constructed by a bitwise inclusive OR of +flags from the following list. +.TP +.B BUS_RDONLY +The process will only be using the bus for receiving messages. +.TP +.B BUS_WRONLY +The process will only be using the bus for sending messages. +.TP +.B BUS_RDWR +The process will use the bus for both receiving and sending messages. +.SH RETURN VALUES +Upon successful completion the function returns 0. Otherwise the +function returns -1 and set \fIerrno\fP to indicate the error. +.SH ERRORS +.TP +.B ENOMEM +The process cannot allocate more memory. +.TP +.B EACCES +Operation permission is denied to the calling process. +.TP +.B EINVAL +The described bus does not exist. +.PP +The +.BR bus_open () +function may also fail and set \fIerrno\fP to any of the errors +specified for the routine +.BR open (2). +.SH SEE ALSO +.BR bus-create (1), +.BR bus (5), +.BR libbus (7), +.BR bus_close (3), +.BR bus_unlink (3), +.BR bus_write (3), +.BR bus_read (3), +.BR bus_poll (3), +.BR open (2) diff --git a/bus_poll.3 b/bus_poll.3 new file mode 100644 index 0000000..f916237 --- /dev/null +++ b/bus_poll.3 @@ -0,0 +1,79 @@ +.TH BUS_POLL 3 BUS +.SH NAME +bus_poll_start, bus_poll_stop, bus_poll, bus_poll_timed - Wait a message to be broadcasted +.SH SYNOPSIS +.LP +.nf +#include +.P +int bus_poll_start(bus_t *\fIbus\fP); +int bus_poll_stop(const bus_t *\fIbus\fP); +const char *bus_poll(bus_t *\fIbus\fP, int \fIflags\fP); +const char *bus_poll_timed(bus_t *\fIbus\fP, const struct timespec *\fItimeout\fP, clockid_t \fIclockid\fP); +.fi +.SH DESCRIPTION +The +.BR bus_poll () +function waits for a message to be broadcasted on the \fIbus\fP, and return +the message it receives. The function fails if (\fIflags\fP &BUS_NOWAIT) +and there is not already a message waiting on the bus. Received messages +shall be copied and parsed, and acted upon, in a separate thread, and +.BR bus_poll () +or +.BR bus_poll_stop () +called again as soon as possible. +.PP +The +.BR bus_poll_start () +funcion must be called before +.BR bus_poll () +is called for the first time. When the process is done listening on the +bus it must call the +.BR bus_poll_stop () +function. +.PP +The +.BR bus_poll_timed () +function behaves like +.BR bus_poll (), +except if it is not able to read a message within the specified time, +it will fail and set \fIerrno\fP to \fBEAGAIN\fP. The time is specified +as an absolute time using the parameter \fItimeout\fP. The behaviour is +unspecified if \fItimeout\fP is \fINULL\fP. \fItimeout\fP is measured +with the clock whose ID is specified by the \fIclockid\fP parameter. This +clock must be a predicitable clock. +.SH RETURN VALUES +Upon successful completion, the functions +.BR bus_poll_start () +and +.BR bus_poll_stop () +returns 0. Otherwise the functions returns -1 and sets \fIerrno\fP to +indicate the error. +.PP +Upon successful completion, the functions +.BR bus_poll () +and +.BR bus_poll_timed () +returns the received message. Otherwise the function returns \fINULL\fP +and sets \fIerrno\fP to indicate the error. +.SH ERRORS +The +.BR bus_poll (3), +.BR bus_poll_start (3) +and +.BR bus_poll_stop (3) +functions may fail and set \fIerrno\fP to any of the errors specified for +.BR semop (3). +The +.BR bus_poll_timed (3) +function may also set \fIerrno\fP to any of the errors specified for +.BR clock_gettime (3). +.SH SEE ALSO +.BR bus-create (1), +.BR bus (5), +.BR libbus (7), +.BR bus_open (3), +.BR bus_write (3), +.BR bus_read (3), +.BR semop (3), +.BR clock_gettime (3) diff --git a/bus_read.3 b/bus_read.3 new file mode 100644 index 0000000..36fd4c9 --- /dev/null +++ b/bus_read.3 @@ -0,0 +1,71 @@ +.TH BUS_READ 3 BUS +.SH NAME +bus_read, bus_read_timed - Listen for new messages a bus +.SH SYNOPSIS +.LP +.nf +#include +.P +int bus_read(const bus_t *\fIbus\fP, int (*\fIcallback\fP)(const char *\fImessage\fP, void *\fIuser_data\fP), + void *\fIuser_data\fP); +int bus_read_timed(const bus_t *\fIbus\fP, int (*\fIcallback\fP)(const char *\fImessage\fP, void *\fIuser_data\fP), + void *\fIuser_data\fP, const struct timespec *\fItimeout\fP, clockid_t \fIclockid\fP); +.fi +.SH DESCRIPTION +The +.BR bus_read () +function waits for new message to be sent on the bus specified in +\fIbus\fP, as provieded by a previous call to the +.BR bus_open () +function. Once a message is received, the \fIcallback\fP function is +invoked. The \fImessage\fP argument to the callback is the received +message, and \fIuser_data\fP for \fIcallback\fP should be +\fIuser_data\fP from +.BR bus_read (). +However, once +.BR bus_read () +has ensured that it will receive any message sent on the bus, it shall +invoke the \fIcallback\fP function with \fImessage\fP set to \fINULL\fP, +to notify the process that it can perform any action that requires that +it is listening on the bus. +.PP +After \fIcallback\fP returns, \fImessage\fP may be override. Therefore +\fIcallback\fP should copy \fImessage\fP and start a new thread that +uses the copy of \fImessage\fP. \fIcallback\fP shall return -1 on +failure, 0 if +.BR bus_read () +should stop listening or 1 if +.BR bus_read () +should continue listening. +.PP +The +.BR bus_read_timed () +function behaves like +.BR bus_read (), +except it will automatically fail and set \fIerrno\fP to \fBEAGAIN\fP +when the specified time has passed. The time is specified as an +absolute time using the parameter \fItimeout\fP. The behaviour is +unspecified if \fItimeout\fP is \fINULL\fP. \fItimeout\fP is measured +with the clock whose ID is specified by the \fIclockid\fP parameter. +This clock must be a predicitable clock. +.SH RETURN VALUES +Upon successful completion, these functions returns 0. Otherwise the +function returns -1 and sets \fIerrno\fP to indicate the error. +.SH ERRORS +The +.BR bus_read (3) +function may fail and set \fIerrno\fP to any of the errors specified for +.BR semop (3). +The +.BR bus_read_timed (3) +function may also set \fIerrno\fP to any of the errors specified for +.BR clock_gettime (3). +.SH SEE ALSO +.BR bus-create (1), +.BR bus (5), +.BR libbus (7), +.BR bus_open (3), +.BR bus_write (3), +.BR bus_poll (3), +.BR semop (3), +.BR clock_gettime (3) diff --git a/bus_unlink.3 b/bus_unlink.3 new file mode 100644 index 0000000..bd1891a --- /dev/null +++ b/bus_unlink.3 @@ -0,0 +1,48 @@ +.TH BUS_UNLINK 3 BUS +.SH NAME +bus_unlink - Remove a bus +.SH SYNOPSIS +.LP +.nf +#include +.P +int bus_unlink(const char *\fIfile\fP); +.fi +.SH DESCRIPTION +The +.BR bus_unlink () +function removes the bus assoicated with the pathname stored in +\fIfile\fP. The function also unlinks the file. +.SH RETURN VALUES +Upon successful completion, the function returns 0. Otherwise the +function returns -1 and sets \fIerrno\fP to indicate the error. +.SH ERRORS +.TP +.B EINVAL +The bus does not exist. +.TP +.B EACCES +Operation permission is denied to the calling process. +.TP +.B EPERM +The user does not have permission to remove the bus. +.PP +The +.BR bus_unlink (3) +function may also fail and set \fIerrno\fP to any of the errors +specified for the routines +.BR unlink (2), +.BR open (2), +.BR semget (3) +and +.BR shmget (3). +.SH SEE ALSO +.BR bus-create (1), +.BR bus (5), +.BR libbus (7), +.BR bus_create (3), +.BR bus_close (3), +.BR unlink (2), +.BR open (2), +.BR semget (3), +.BR shmget (3) diff --git a/bus_write.3 b/bus_write.3 new file mode 100644 index 0000000..833396e --- /dev/null +++ b/bus_write.3 @@ -0,0 +1,56 @@ +.TH BUS_WRITE 3 BUS +.SH NAME +bus_write, bus_write_timed - Broadcast a message a bus +.SH SYNOPSIS +.LP +.nf +#include +.P +int bus_write(const bus_t *\fIbus\fP, const char *\fImessage\fP, int \fIflags\fP); +int bus_write_timed(const bus_t *\fIbus\fP, const char *\fImessage\fP, + const struct timespec *\fItimeout\fP, clockid_t \fIclockid\fP); +.fi +.SH DESCRIPTION +The +.BR bus_write () +function broadcasts a message on the bus whose information is stored in +\fIbus\fP. The message read by the function is stored in the parameter +\fImessage\fP. It may not exceeed 2048 bytes, including NULL termination. +.PP +The +.BR bus_write () +function shall fail, and set \fIerrno\fP to \fIEAGAIN\fP, if the call +would suspend the process and (\fIflags\fP &BUS_NOWAIT). +.PP +The +.BR bus_write_timed () +function behaves like +.BR bus_write (), +except if it is not able to write the \fImessage\fP within the specified +time, it will fail and set \fIerrno\fP to \fBEAGAIN\fP. The time is +specified as an absolute time using the parameter \fItimeout\fP. The +behaviour is unspecified if \fItimeout\fP is \fINULL\fP. \fItimeout\fP +is measured with the clock whose ID is specified by the \fIclockid\fP +parameter. This clock must be a predicitable clock. +.SH RETURN VALUES +Upon successful completion, these functions returns 0. Otherwise the +function returns -1 and sets \fIerrno\fP to indicate the error. +.SH ERRORS +The +.BR bus_write (3) +function may fail and set \fIerrno\fP to any of the errors specified for +.BR semop (3). +The +.BR bus_write_timed (3) +function may also set \fIerrno\fP to any of the errors specified for +.BR clock_gettime (3). +.SH SEE ALSO +.BR bus-create (1), +.BR bus (5), +.BR libbus (7), +.BR bus_open (3), +.BR bus_read (3), +.BR bus_poll (3), +.BR bus_chown (3), +.BR bus_chmod (3), +.BR clock_gettime (3) diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..6f9acdb --- /dev/null +++ b/config.mk @@ -0,0 +1,6 @@ +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS = -std=c99 -Wall -Wextra -pedantic -O2 $(CPPFLAGS) +LDFLAGS = -s -lrt diff --git a/dist/arch/stable/.gitignore b/dist/arch/stable/.gitignore deleted file mode 100644 index ccea0b1..0000000 --- a/dist/arch/stable/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -* -!/.git* -*~ -!/PKGBUILD -!/bus.install - diff --git a/dist/arch/stable/PKGBUILD b/dist/arch/stable/PKGBUILD deleted file mode 100644 index 1cfedf5..0000000 --- a/dist/arch/stable/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Mattias Andrée <`base64 -d`(bWFhbmRyZWUK)@member.fsf.org> - -pkgname=bus -pkgver=3.1.6 -pkgrel=2 -pkgdesc="A simple daemonless system for broadcasting messages locally" -arch=(i686 x86_64) -url="https://github.com/maandree/bus" -license=('MIT') -depends=() -makedepends=() -install=bus.install -source=($url/archive/$pkgver.tar.gz) -sha256sums=(79683d48e8bcea44a564510b27d438ebf46a1080e8d51c6d2eeae324b87b087a) - - -build() { - cd "$srcdir/$pkgname-$pkgver" - make PREFIX=/usr -} - - -package() { - cd "$srcdir/$pkgname-$pkgver" - make PREFIX=/usr install DESTDIR="$pkgdir" -} - diff --git a/dist/arch/stable/bus.install b/dist/arch/stable/bus.install deleted file mode 100644 index 6b67f03..0000000 --- a/dist/arch/stable/bus.install +++ /dev/null @@ -1,20 +0,0 @@ -_file="bus" - -infodir="usr/share/info" -file="${_file}.info" - - -post_install() { - [[ -x "usr/bin/install-info" ]] || return 0 - install-info -- "${infodir}/${file}" "${infodir}/dir" 2> /dev/null -} - -post_upgrade() { - post_install "$1" -} - -pre_remove() { - [[ -x "usr/bin/install-info" ]] || return 0 - install-info --delete -- "${infodir}/${file}" "${infodir}/dir" 2> /dev/null -} - diff --git a/doc/info/bus.texinfo b/doc/info/bus.texinfo deleted file mode 100644 index be50cea..0000000 --- a/doc/info/bus.texinfo +++ /dev/null @@ -1,2079 +0,0 @@ -\input texinfo @c -*-texinfo-*- - -@c %**start of header -@setfilename bus.info -@settitle bus -@afourpaper -@documentencoding UTF-8 -@documentlanguage en -@finalout -@c %**end of header - - -@dircategory Interprorcess Communication -@direntry -* bus: (bus). A simple daemonless system for broadcasting messages locally -@end direntry - - -@copying -Copyright @copyright{} 2015 Mattias Andrée - -@quotation -Permission is granted to copy, distribute and/or modify this document -under the terms of the GNU Free Documentation License, Version 1.3 or -any later version published by the Free Software Foundation; with no -Invariant Sections, with no Front-Cover Texts, and with no Back-Cover -Texts. A copy of the license is included in the section entitled -``GNU Free Documentation License''. -@end quotation -@end copying - -@ifnottex -@node Top -@top bus -- A simple daemonless system for broadcasting messages locally -@insertcopying -@end ifnottex - -@titlepage -@title bus -@subtitle A simple daemonless system for broadcasting messages locally -@author by Mattias Andrée (maandree) - -@page -@vskip 0pt plus 1filll -@insertcopying -@page -@end titlepage - -@contents - - -@iftex -@macro xrm{} -@rm{} -@end macro -@macro xtt{} -@tt{} -@end macro -@end iftex - -@ifnottex -@macro xrm{} -@end macro -@macro xtt{} -@end macro -@end ifnottex - -@menu -* Overview:: Brief overview of @command{bus}. -* Standard:: How to use @command{bus} properly. -* Invoking:: Executing @command{bus}. -* Interface:: Using @command{libbus}. -* Protocol:: How communication over @command{bus} works internally. -* Rationale:: Why @command{bus}? -* Examples:: Usecase examples and API-demonstration. -* GNU Free Documentation License:: Copying and sharing this manual. - -@detailmenu - --- The Detailed Node Listing --- - -Invoking - -* bus create:: Create a bus. -* bus remove:: Remove a bus. -* bus listen:: Listen for new message on a bus. -* bus wait:: Listen for one new message only on a bus. -* bus broadcast:: Broadcast a message on a bus. -* bus chmod:: Change permissions on a bus. -* bus chown:: Change ownership of a bus. -* bus chgrp:: Change group ownership of a bus. - -Examples - -* Audio-volume control:: -* Telephony and music:: -* Timed:: -* Nonblocking:: -* Daemon-dependencies:: - -@end detailmenu -@end menu - - - -@node Overview -@chapter Overview - -@command{bus} is a stupid-simple, thrilless, daemonless interprocess -communication system for broadcasting messages. It is a lightweight -alternative to a two-phase interprocess flexible barrier. - -@command{bus} uses a System V semaphore array and System V shared -memory. Buses are named; the key of the semaphore array and the -shared memory is stored in a regular file. - -The shared memory used by @command{bus} is always 2048 bytes. -Additionally all messages should be encoded in UTF-8 and not contain -any NULL characters, except they @emph{must} always end with a NULL -byte. Furthermore messages should be prefixed with the process -identifer of the process whence the message originated, followed -by a space. If the process is ephemeral@footnote{The process exits -after the broadcast, or shortly thereafter.}, 0 should be used -instead of the process identifier. - -Communication over @command{bus} is synchronous. The broadcast call -does not return until all listeners have received (and copied) the -message. A malfunctioning program can lock the bus. - -This software package contains a C library and a command line -utility. The package python-bus provides a Python 3 module. - - - -@node Standard -@chapter Standard - -The command @command{bus create} can be used to create new buses. By -convention, buses should be stored in @file{$XDG_RUNTIME_DIR/bus}, -this is what @command{bus create} does if no pathname is given. The -pathname of the bus should be tracked using @env{BUS_X}, where @env{X} -is replaced with either: - -@table @env -@item GENERIC -For the bus used in generic cases. That is all but the cases -of the buses listed below. -@item AUDIO -For the bus used in with the audio subsystem is involved. -@item VIDEO -For the bus used in with the video subsystem is involved. -@item INPUT -For the bus used in with the input subsystem is involved. -@item FILES -For the bus used in with the storage subsystem is involved. -@end table - -This list may be extended in the future. Therefore, and for -other conventions, project-private buses should be tracked -using @env{X_BUS}, where @env{X} is the project name. - -Messages broadcasted on a bus cannot be longer than 2047 bytes, -excluding NUL termination. Message should be encoded in UTF-8, -and most not contain the NUL character. - -Broadcasted message should start with the process ID whence -the message originated, followed by a single regular space. -If the process is ephemeral@footnote{The process exits after -the broadcast, or shortly thereafter.}, 0 should be used instead -of the process identifier. - - - -@node Invoking -@chapter Invoking - -@command{bus} includes the following commands: - -@table @command -@item create -Create a bus. -See @ref{bus create} for more information. -@item remove -Remove a bus. -See @ref{bus remove} for more information. -@item listen -Listen for new message on a bus. -See @ref{bus listen} for more information. -@item wait -Listen for one new message only on a bus. -See @ref{bus wait} for more information. -@item broadcast -Broadcast a message on a bus. -See @ref{bus broadcast} for more information. -@item chmod -Change permissions on a bus. -See @ref{bus chmod} for more information. -@item chown -Change ownership of a bus. -See @ref{bus chown} for more information. -@item chgrp -Change group ownership of a bus. -See @ref{bus chgrp} for more information. -@end table - -Upon successful completion, these commands exit with the value -0. On failure, they exit with the value 1. If the command is -not recognised the exit value is 2. - -@menu -* bus create:: Create a bus. -* bus remove:: Remove a bus. -* bus listen:: Listen for new message on a bus. -* bus wait:: Listen for one new message only on a bus. -* bus broadcast:: Broadcast a message on a bus. -* bus chmod:: Change permissions on a bus. -* bus chown:: Change ownership of a bus. -* bus chgrp:: Change group ownership of a bus. -@end menu - - - -@node bus create -@section @command{bus create} - -The syntax for invocation of @command{bus create} is -@example -bus create [-x] [--] [@var{PATHNAME}] -@end example - -The command creates a bus and stores the key to it in the -file @var{PATHNAME}. If @var{PATHNAME} is omitted, a -random pathname in @file{$XDG_RUNTIME_DIR/bus} will be -used and printed to stdout. - -If @option{-x} is used, the command will fail if -the file @var{PATHNAME} already exists. - - - - -@node bus remove -@section @command{bus remove} - -The syntax for invocation of @command{bus remove} is -@example -bus remove [--] @var{PATHNAME} -@end example - -The command removes the bus whose key is stored in -the file @var{PATHNAME}. The file holding the -key is also unlinked. - - - -@node bus listen -@section @command{bus listen} - -The syntax for invocation of @command{bus command} is -@example -bus listen [--] @var{PATHNAME} @var{COMMAND} -@end example - -The command listens for new messages on the bus whose -key is stored in the file @var{PATHNAME}. Once a message -is received, @var{COMMAND} will be spawned with the -environment variable @env{msg} (lowercased) set to the -received message. @sc{POSIX} shell syntax applies to -@var{COMMAND}. - - -@node bus wait -@section @command{bus wait} - -The syntax for invocation of @command{bus wait} is -@example -bus wait [--] @var{PATHNAME} @var{COMMAND} -@end example - -The command listens for a new message on the bus whose -key is stored in the file @var{PATHNAME}. Once a message -is received, the process will stop listening for more -messages and @var{COMMAND} will be spawned with the -environment variable @env{msg} (lowercased) set to the -received message. @sc{POSIX} shell syntax applies to -@var{COMMAND}. - - - -@node bus broadcast -@section @command{bus broadcast} - -The syntax for invocation of @command{bus broadcast} is -@example -bus broadcast [-n] [--] @var{PATHNAME} @var{MESSAGE} -@end example - -The command broadcasts the message @var{MESSAGE} on the -bus whose key is stored in the file @var{PATHNAME}. - - - -@node bus chmod -@section @command{bus chmod} - -The syntax for invocation of @command{bus chmod} is -@example -bus chmod [--] @var{PERMISSIONS} @var{PATHNAME} -@end example - -This command changes who have access to the bus whose key -is stored in the file @var{PATHNAME}. In the permissions, -the owner, the group, and others (not in tgroup) are -represented by the symbols @code{u}@footnote{@code{u} -stands for `user'.}, @code{g}, and @code{o}, respectively. -The permissions string is imagined to have always be -prefixed with an @code{=}. This symbols means that all user -classes list after it, and only those classes, as permission -to use the bus. Similarly the symbols @code{+} and @code{-} -can be used to grant and revoke access, respectively. The -symbols @code{=}, @code{+}, and @code{-} can be mixed, and -are interpreted from left to right. Alternatively the -permissions string can be a octal number, where the owner -is represented by any bit in 700 (100, 200, or 400, or any -combination thereof), the group is represented by any bit -in 70, and others (not in the group) is represented by any -bit in 7. - -The current permission of the bus can be retrieved by -running @command{stat} over the file @var{PATHNAME}. - - - -@node bus chown -@section @command{bus chown} - -The syntax for invocation of @command{bus chown} is -@example -bus chown [--] @var{OWNER}[:@var{GROUP}] @var{PATHNAME} -@end example - -This command changes the owner, that owns the bus whose -key is stored in the file @var{PATHNAME}, to the specified -owner. The owner can be specified either with a numerical -user identifier or with a user name. If a group is -specified, the bus's owner-group will be set to that group, -otherwise the group will remain unchanged (not changed -to the group of the new owner.) The group can be specified -either with a numerical group identifier or with a group -name. - -The current ownership of the bus can be retrieved by -running @command{stat} over the file @var{PATHNAME}. - - - -@node bus chgrp -@section @command{bus chgrp} - -The syntax for invocation of @command{bus chgrp} is -@example -bus chgrp [--] @var{GROUP} @var{PATHNAME} -@end example - -This command changes the group, that owns the bus whose -key is stored in the file @var{PATHNAME}, to the specified -group. The group can be specified either with a numerical -group identifier or with a group name. - -The current ownership of the bus can be retrieved by -running @command{stat} over the file @var{PATHNAME}. - - - -@node Interface -@chapter Interface - -To use @command{libbus} in your C program, include the -header file @file{} and link with the flag -@option{-lbus}. - -With exception to @code{bus_poll} and @code{bus_poll_timed}, -all functions return @code{0} upon successful completion, -and @code{-1} in case of failure. @code{bus_poll} and -@code{bus_poll_timed} return @code{NULL} on failure. -On failure on all functions set @code{errno} to indicate -what went wrong. - -@file{} defines the following functions: - -@table @code -@item int bus_create(const char *file, int flags, char **out_file) -This function creates a bus with the asscoiated pathname -specifed by the value of the parameter @code{file}. If -@code{file} is @code{NULL} a random pathname is selected. -This pathname adheres to the convention set forth by -in @ref{Standard}. - -If @code{file} is not @code{NULL} the function fails if the -file already exists if @code{flags} contains @code{BUS_EXCL}. -Otherwise if @code{file} is not @code{NULL}, the function -does nothing if the file already exists. - -If @code{flags} contains @code{BUS_INTR}, the function fails -if it is interrupted. - -Unless @code{out_file} is NULL, the pathname of the bus -should be stored in a new char array stored in @code{*out_file}. -The caller must free the allocated stored in @code{*out_file}. - -If the processes cannot allocate enough memory to perform -the action, the function sets @code{errno} to @code{ENOMEM} -and fails. It may also fail and set @code{errno} to any of -the errors specified for the system calls @code{open} and -@code{write}. - -@item int bus_unlink(const char *file) -This function removes the bus assoicated with the pathname -stored in the parameter @code{file}. The function also -unlinks the file. - -The function may set @code{errno} to any of the following -values and fail for the specified reasons: -@table @code -@item EINVAL -The bus does not exist. -@item EACCES -Operation permission is denied to the calling process. -@item EPERM -The user does not have permission to remove the bus. -@end table -@noindent -It may also fail and set @code{errno} to any of the errors -specified for the system calls @code{unlink} and @code{open}, -and the functions @code{semget} and @code{shmget}. - -@item int bus_open(bus_t *bus, const char *file, int flags) -This function acquires resources required for the process -to use the bus associated with the filename stored in the -parameter @code{file}. The function also stores the resources -in @code{bus} for use by other @command{bus} functions. - -Values for @code{flags} are constructed by a bitwise -inclusive @sc{or} of flags from the following list. -@table @code -@item BUS_RDONLY -The process will only be using the bus for receiving messages. -@item BUS_WRONLY -The process will only be using the bus for sending messages. -@item BUS_RDWR -The process will use the bus for both receiving and sending -messages. -@end table - -The function may set @code{errno} to any of the following -values and fail for the specified reasons: -@table @code -@item ENOMEM -The process cannot allocate enough memory to perform the -action. -@item EACCES -Operation permission is denied to the calling process. -@item EINVAL -The described bus does not exist. -@end table -@noindent -It may also fail and set @code{errno} to any of the errors -specified for the system call @code{open}. - -@item int bus_close(bus_t *bus) -This function disposes of resources allocated to the -process, as referenced in the parameter @code{bus}. - -The function fails and sets @code{errno} to @code{EINVAL} -if the bus does not exist. - -@item int bus_write(const bus_t *bus, const char *message, int flags) -This function broadcasts a message on the bus whose -information is stored in the parameter @code{bus}. The -message read by the function is stored in the parameter -@code{message}. It may not exceeed 2048 bytes, including -NUL termination. - -The function shall fail, and set @code{errno} to -@code{EAGAIN}, if the call would suspend the process and -@code{flags} contains @code{BUS_NOWAIT}. - -The function may fail and set @code{errno} to any of the -errors specified for the function @code{semop}. - -@item int bus_write_timed(const bus_t *bus, const char *message, const struct timespec *timeout, clockid_t clockid) -This function behaves like @code{bus_write}, except if it -is not able to write the message within the specified time, -it will fail and set @code{errno} to @code{EAGAIN}. The -time is specified as an absolute time using the parameter -@code{timeout}. The behaviour is unspecified if @code{timeout} -is @code{NULL}. @code{timeout} is measured with the clock whose -identifier is specified by the parameter @code{clockid}. This -clock must be a predicitable clock@footnote{There are probably -other, undocumented, seemingly arbitrary restrictions too.}. - -The function may fail and set @code{errno} to any of the -errors specified for the functions @code{semop} and -@code{clock_gettime}. - -@item int bus_read(const bus_t *bus, int (*callback)(const char *message, void *user_data), void *user_data) -This function waits for new message to be sent on the bus -specified in the @code{bus} parameter, as provieded by a -previous call to the function @code{bus_open}. Once a -message is received, the parameter-function @code{callback} -is invoked. The parameter @code{message} in @code{callback} -is the received message, and @code{user_data} in -@code{callback} should be @code{user_data} from @code{bus_read}. -However, once the function [@code{bus_read}] has ensured -that it will receive any message sent on the bus, it shall -invoke the parameter-function @code{callback} with -@code{message} set to @code{NULL}, to notify the process that -it can perform any action that requires that it is listening -on the bus. - -After @code{callback} returns, @code{message} may be override. -Therefore @code{callback} should copy message and start a new -thread that uses the copy of @code{message}. @code{callback} -shall return @code{-1} on failure, @code{0} if the function -[@code{bus_read}] should stop listening, or @code{1} if -the function should continue listening. - -The function may fail and set @code{errno} to any of the -errors specified for the function @code{semop}. - -@item int bus_read_timed(const bus_t *bus, int (*callback)(const char *message, void *user_data), void *user_data, const struct timespec *timeout, clockid_t clockid) -This function behaves like @code{bus_read}, except it will -automatically fail and set @code{errno} to @code{EAGAIN} when -the specified time has passed. The time is specified as an -absolute time using the parameter @code{timeout}. The -behaviour is unspecified if @code{timeout} is @code{NULL}. -@code{timeout} is measured with the clock whose identifier -is specified by the parameter @code{clockid}. This clock -must be a predicitable clock@footnote{There are probably -other, undocumented, seemingly arbitrary restrictions too.}. - -The function may fail and set @code{errno} to any of the -errors specified for the functions @code{semop} and -@code{clock_gettime}. - -@item int bus_poll_start(bus_t *bus) -@itemx int bus_poll_stop(const bus_t *bus) -@itemx const char *bus_poll(bus_t *bus, int flags) -@itemx const char *bus_poll_timed(bus_t *bus, const struct timespec *timeout, clockid_t clockid) -The function @code{bus_poll} waits for a message to be -broadcasted on the bus, and return the message it receives. -The function fails if @code{flags} contains @code{BUS_NOWAIT} -and there is not already a message waiting on the bus. -Received messages shall be copied and parsed, and acted -upon, in a separate thread, and the function @code{bus_poll} -or the function @code{bus_poll_stop} called again as soon -as possible. - -The funcion @code{bus_poll_start} must be called before -@code{bus_poll} is called for the first time. When the -process is done listening on the bus, it must call the -function @code{bus_poll_stop}. - -The function @code{bus_poll_timed} behaves like the function -@code{bus_poll}, except if it is not able to read a message -within the specified time, it will fail and set @code{errno} -to @code{EAGAIN}. The time is specified as an absolute time -using the parameter @code{timeout}. The behaviour is -unspecified if @code{timeout} is @code{NULL}. @code{timeout} -is measured with the clock whose identifier is specified by -the parameter @code{clockid}. This clock must be a predicitable -clock@footnote{There are probably other, undocumented, -seemingly arbitrary restrictions too.}. - -Upon successful completion, the functions @code{bus_poll} and -@code{bus_poll_timed} returns the received message. - -These functions may fail and set @code{errno} to any of the -errors specified for the function @code{semop}. The function -@code{bus_poll_timed} may also set @code{errno} to any of -the errors specified for @code{clock_gettime}. - -@item int bus_chown(const char *file, uid_t owner, gid_t group) -This function changes the owner and the group of the bus, -associated with the file whose pathname is stored in the -parameter @code{file}, to the owner and group specified by -the parameters @code{owner} and @code{group}, respectively. - -The current ownership of a bus can be retrieved by calling -@code{stat} over the pathname of the bus. - -The function may fail and set @code{errno} to any of the -errors specified for the functions @code{bus_open}, -@code{chown}, @code{semget}, @code{shmget}, and @code{shmctl} -as well as any errors specified for the commands -@code{IPC_STAT} and @code{IPC_SET} for the function -@code{semctl}. - -@item int bus_chmod(const char *file, mode_t mode) -This function gives access to the bus associated with the -file whose pathname is stored in the parameter @code{file} -according to the following rules: - -@itemize @bullet{} -@item -If @code{mode} contains any of the bits @code{S_IRWXU} -contains, the owner should be given full access to the -bus. Otherwise the owner should have no access. -@item -If @code{mode} contains any of the bits @code{S_IRWXG} -contains, the group should be given read and write -access to the bus. Otherwise the group should have no -access. -@item -If @code{mode} contains any of the bits @code{S_IRWXO} -contains, users that are neither the owner nor member -of the group should be given read and write access to -the bus. Otherwise they should have no access. -@end itemize - -The current permissions of a bus can be retrieved by calling -@code{stat} over the pathname of the bus. - -The function may fail and set @code{errno} to any of the -errors specified for the functions @code{bus_open}, -@code{chmode}, @code{semget}, @code{shmget}, and @code{shmctl} -as well as any errors specified for the commands -@code{IPC_STAT} and @code{IPC_SET} for the function -@code{semctl}. -@end table - -There is not reason for poking around in @code{bus_t} -(@code{struct bus}). It should be considered opaque. -You can read the documentation in @file{} if -you want to know what is in it. - - - - -@node Protocol -@chapter Protocol - -@command{bus} is built upon following three procedures. - -@noindent -@code{create} -@example -@w{@xrm{}Select a filename.@xtt{}} - -@w{@xrm{}Create XSI semaphore array @{@code{S} = 0, @code{W} = 0, @code{X} = 1, @code{Q} = 0, @code{N} = 0@}@xtt{}} -@w{@xrm{}with random key. Store the semaphore array's key in decimal form@xtt{}} -@w{@xrm{}on the first line in the selected file.@xtt{}} - -@w{@xrm{}Create XSI shared memory, with an allocation of 2048 bytes, with@xtt{}} -@w{@xrm{}a random key. Store the shared memory's key in decimal form on@xtt{}} -@w{@xrm{}the second line in the selected file.@xtt{}} -@end example - -@noindent -@code{broadcast} -@example -with P(X): - Z(W) - @w{@xrm{}Write NUL-terminate message to shared memory@xtt{}} - with V(N): -- (1) - Q := 0 - Z(S) - --- (1) @w{@xrm{}may be omitted if semaphores are known that@xtt{}} - @w{P()@xrm{}, @xtt{}Z()@xrm{}, @xtt{}V()@xrm{} cannot create a race condition@xtt{}} - @w{@xrm{}with a processes running @xtt{}Z()@xrm{}.@xtt{}} -@end example - -@noindent -@code{listen} -@example -with V(S): - forever: - V(Q) - Z(Q) - @w{@xrm{}Read NUL-terminated message from shared memory@xtt{}} - if breaking: - break - with V(W): - with P(S): - Z(S) - Z(N) -@end example - -@noindent -@code{V(a)} means that semaphore a is released.@* -@code{P(a)} means that semaphore a is acquired.@* -@code{Z(a)} means that the process waits for semaphore a to become 0.@* -@code{with P(a)} that @code{P(a)} is done before the entering the scope, -and @code{V(a)} is done when exiting the scope. It also means that -these actions [@code{P(a)} and @code{V(a)}] are undone when the process -exits, or if the call fails.@* -@code{with V(a)} is to @code{V(a)} as @code{with P(a)} is to @code{P(a)}. - - - -@node Rationale -@chapter Rationale - -We need an interprocess communication system similar -to message queues. But we need broadcasting rather than -anycasting, so we have a fast, simple and daemonless -system for announcing events to any processes that -might be interested. - - - -@node Examples -@chapter Examples - -This chapter contains usecase examples and -API-demonstrations. You will find that they are (on -a standard installation) installed on your system. - -@menu -* Audio-volume control:: -* Telephony and music:: -* Timed:: -* Nonblocking:: -* Daemon-dependencies:: -@end menu - - - - -@node Audio-volume control -@section Audio-volume control - -Assume you have program that display the audio volume. -This program checks every second third if the volume -have changed. - -Also assume that you use @command{amixer} to change the -volume, most often by using keybindings via @command{xbindkeys}. - -To reduce the delay, you want to send a signal to the -monitor program that the volume have changed. For this -more primitive IPC is sufficient, but lets assume there -are other programs interested in this information too. - -To accomplish this, you create a wrapper for @command{amixer} -that broadcasts updates on a bus. This wrapper is -installed as @command{~/.local/bin/amixer}, and -@command{~/.local/bin/} is included in @env{$PATH} -before @command{/usr/bin}. -@* - -@noindent -Before starting run @command{./init}, this code is -should be run from your profile file if you want to -implement this on your system. After running -@command{./init}, you can start one or more listeners -by running @command{./alsa-monitor}. - -To change the volume run -@code{./amixer -c 0 -- set Master 5%+} or similar. - -When you are done run @command{./cleanup}. - - -@subsubheading @file{./amixer} -@example -#!/bin/sh -/usr/bin/amixer "$@@" -for arg in "$@@"; do - if [ "$@{arg@}" = "set" ] || \ - [ "$@{arg@}" = "sset" ] || \ - [ "$@{arg@}" = "cset" ]; then - exec bus broadcast "/tmp/example-bus" '0 volume-changed *' - fi -done -@end example - - -@subsubheading @file{./cleanup} -@example -#!/bin/sh -exec bus remove "/tmp/example-bus" -@end example - - -@subsubheading @file{./init} -@example -#!/bin/sh -bus create "/tmp/example-bus" - -# @w{@xrm{}The following code is more suitable in the real world,@xtt{}} -# @w{@xrm{}if used, the other files should use @xtt{}"$@{BUS_AUDIO@}"} -# @w{@xrm{}instead of @xtt{}"/tmp/example-bus"@xrm{}.@xtt{}} -# -# export BUS_AUDIO="$@{XDG_RUNTIME_DIR@}/bus/audio" -# if [ ! -f "$@{BUS_AUDIO@}" ]; then -# bus create "$@{BUS_AUDIO@}" -# fi -@end example - - -@subsubheading @file{./monitor} -@example -#!/bin/sh -if [ $# = 1 ]; then - if [ "$(echo "$@{1@}" | cut -d ' ' -f 2)" = "volume-changed" ]; then - printf '\e[H\e[2J' - amixer get Master - fi - exit 0 -fi - -exec 2>/dev/null - -printf '\e[?1049h\e[H\e[2J' -trap -- "printf '\e[?1049l'" SIGINT -bus listen "/tmp/example-bus" \'"$@{0/\'/\'\\\'\'@}"\'' "$@{msg@}"' -@end example - - - -@node Telephony and music -@section Telephony and music - -Assume you have a music player and a telephony program. -You might like it if the music player pauses whenever -you make or receive a call. You may also like it, if -the music resumed when the call ended. - -In this example we will assume you the have @command{mocp} -(@command{moc} package) running. And we will use the shell to -simulate a telephony program. -@* - -@noindent -First of, run make to build this example. Before -starting run @command{./init}. And when you are -done run @command{./cleanup}. - -In one terminal run @command{./monitor}. This program -will pause @command{mocp} when you make or receive a call, -it will also resume @command{mocp} when all calls have -ended if it did pause @command{mocp}. - -Then start any positive number of terminals. -We will pretend that each of them are telephony -programs. To make or receive a call, run -@command{./receive-or-make-call}, when you want to -end the pretend call, run @command{./end-call} from -the terminal (or more accurately, from the same -process.) - - -@subsubheading @file{./Makefile} -@example -COMMANDS = init cleanup monitor end-call receive-or-make-call - -all: $@{COMMANDS@} -%: %.c - $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< -clean: - -rm $@{COMMANDS@} - -.PHONY: all clean -@end example - - -@subsubheading @file{./cleanup.c} -@example -#include -#include - -int main() -@{ - return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); -@} -@end example - - -@subsubheading @file{./end-call.c} -@example -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static char message[BUS_MEMORY_SIZE]; - -int main() -@{ - bus_t bus; - sprintf(message, "%ji unforce-pause", (intmax_t)getppid()); - /* @w{@xrm{}Yes, PPID; in this example we pretend the shell is the telephony process.@xtt{}} */ - t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); - t (bus_write(&bus, message, 0)); - bus_close(&bus); - return 0; - -fail: - perror("end-call"); - bus_close(&bus); - return 1; -@} -@end example - - -@subsubheading @file{./init.c} -@example -#include -#include - -int main() -@{ - return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); -@} -@end example - - -@subsubheading @file{./monitor.c} -@example -#include -#include -#include -#include - -static size_t pauser_count = 0; -static size_t pausers_size = 0; -static char* pausers = NULL; - -static int is_moc_playing(void) -@{ - return !WEXITSTATUS(system("env LANG=C mocp -i 2>/dev/null |" - "grep 'State: PLAY' >/dev/null")); -@} - -/* @w{@xrm{}In a proper implementation, message whould be copyied, and then@xtt{}} - * @w{@xrm{}a new thread would be created that parsed the copy. But that is@xtt{}} - * @w{@xrm{}too much for an example, especially since it would also require@xtt{}} - * @w{@xrm{}a mutex to make sure two threads do not modify data at the same@xtt{}} - * @w{@xrm{}time, causing chaos.@xtt{}} */ -static int callback(const char *message, void *user_data) -@{ - char *msg = NULL; - size_t len = 0; - if (message == 0) - return 1; - while ((len < 2047) && message[len]) - len++; - msg = malloc((len + 1) * sizeof(char)); - t (msg == NULL); - memcpy(msg, message, len * sizeof(char)); - msg[len] = 0; - /* @w{@xrm{}BEGIN run as in a separate thread@xtt{}} */ - if (pauser_count || is_moc_playing()) @{ - char *begin = strchr(msg, ' '); - ssize_t pid; - int requests_pause; - if (begin == NULL) - goto done; - *begin++ = 0; - pid = (ssize_t)atoll(msg); - if (pid < 1) /* @w{@xrm{}We need a real PID, too bad there is@xtt{}} - * @w{@xrm{}no convient way to detect if it dies.@xtt{}} */ - goto done; - if ((strstr(begin, "force-pause ") == begin) || - !strcmp(begin, "force-pause")) - requests_pause = 1; - else if ((strstr(begin, "unforce-pause ") == begin) || - !strcmp(begin, "unforce-pause")) - requests_pause = 0; - else - goto done; - if ((size_t)pid >= pausers_size) @{ - pausers = realloc(pausers, (size_t)(pid + 1) * sizeof(char)); - t (pausers == NULL); /* @w{@xrm{}Let's ignore the memory leak.@xtt{}} */ - memset(pausers + pausers_size, 0, - ((size_t)(pid + 1) - pausers_size) * sizeof(char)); - pausers_size = (size_t)(pid + 1); - @} - if (pausers[pid] ^ requests_pause) @{ - pauser_count += requests_pause ? 1 : -1; - pausers[pid] = requests_pause; - if (pauser_count == (size_t)requests_pause) - system(requests_pause ? "mocp -P" : "mocp -U"); - @} - @} - /* @w{@xrm{}END run as in a separate thread@xtt{}} */ - goto done; - (void) user_data; - -fail: - perror("monitor"); - return -1; - -done: - free(msg); - return 1; -@} - -int main() -@{ - bus_t bus; - t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); - t (bus_read(&bus, callback, NULL)); - bus_close(&bus); - free(pausers); - return 0; - -fail: - perror("monitor"); - bus_close(&bus); - free(pausers); - return 1; -@} -@end example - - -@subsubheading @file{./receive-or-make-call.c} -@example -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static char message[BUS_MEMORY_SIZE]; - -int main() -@{ - bus_t bus; - sprintf(message, "%ji force-pause", (intmax_t)getppid()); - /* @w{@xrm{}Yes, PPID; in this example we pretend the shell is the telephony process.@xtt{}} */ - t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); - t (bus_write(&bus, message, 0)); - bus_close(&bus); - return 0; - -fail: - perror("receive-or-make-call"); - bus_close(&bus); - return 1; -@} -@end example - - - -@node Timed -@section Timed - -This example shows how to use timed operations. - -First of, run make to build this example. - -To start the example run @command{./init}. When you are -done run @command{./cleanup}. - -Running instances of @command{./poll} will wait for new -messages continuously, but with one second timeouts. - -@command{./slow-poll} works like @command{./poll}, except -it will sleep for one second every time it receives -a message. - -Running instances of @command{./read} will read for ten -seconds and then time out. - -@command{./poll}, @command{./read}, and @command{./slow-poll} -will stop if the message "stop" is broadcasted. - -@command{./write} will wait for atmost a tenth of a -seconds before failing. This means that if two instances -of @command{./write} is started at the same time one of -them will fail if @command{./slow-poll} is running. - -@command{./poll}, @command{./read}, @command{./init} and -@command{./cleanup} are run without any additional -arguments. @command{./write} is run with the message -as the second argument. - - -@subsubheading @file{./Makefile} -@example -COMMANDS = init cleanup write poll read slow-poll - -all: $@{COMMANDS@} -%: %.c - $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< -clean: - -rm $@{COMMANDS@} - -.PHONY: all clean -@end example - - -@subsubheading @file{./cleanup.c} -@example -#include -#include - -int main() -@{ - return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); -@} -@end example - - -@subsubheading @file{./init.c} -@example -#include -#include - -int main() -@{ - return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); -@} -@end example - - -@subsubheading @file{./poll.c} -@example -#include -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -int main() -@{ - bus_t bus; - const char *message; - long long tick = 0; - struct timespec timeout; - t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); - t (bus_poll_start(&bus)); - for (;;) @{ - t (clock_gettime(CLOCK_MONOTONIC, &timeout)); - timeout.tv_sec += 1; - message = bus_poll_timed(&bus, &timeout, CLOCK_MONOTONIC); - if (message == NULL) @{ - t (errno != EAGAIN); - printf("waiting... %lli\n", ++tick); - continue; - @} - tick = 0; - message = strchr(message, ' ') + 1; - if (!strcmp(message, "stop")) - break; - printf("\033[01m%s\033[21m\n", message); - @} - t (bus_poll_stop(&bus)); - bus_close(&bus); - return 0; - -fail: - perror("poll"); - bus_poll_stop(&bus); - bus_close(&bus); - return 1; -@} -@end example - - -@subsubheading @file{./read.c} -@example -#include -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static int callback(const char *message, void *user_data) -@{ - (void) user_data; - - if (message == NULL) - return 1; - - message = strchr(message, ' ') + 1; - if (!strcmp(message, "stop")) - return 0; - printf("%s\n", message); - return 1; -@} - -int main() -@{ - bus_t bus; - struct timespec timeout; - t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); - t (clock_gettime(CLOCK_MONOTONIC, &timeout)); - timeout.tv_sec += 10; - t (bus_read_timed(&bus, callback, NULL, &timeout, CLOCK_MONOTONIC)); - bus_close(&bus); - return 0; - -fail: - perror("poll"); - bus_poll_stop(&bus); - bus_close(&bus); - return 1; -@} -@end example - - -@subsubheading @file{./slow-poll.c} -@example -#include -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -int main() -@{ - bus_t bus; - const char *message; - long long tick = 0; - struct timespec timeout; - t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); - t (bus_poll_start(&bus)); - for (;;) @{ - t (clock_gettime(CLOCK_MONOTONIC, &timeout)); - timeout.tv_sec += 1; - message = bus_poll_timed(&bus, &timeout, CLOCK_MONOTONIC); - if (message == NULL) @{ - t (errno != EAGAIN); - printf("waiting... %lli\n", ++tick); - continue; - @} - tick = 0; - message = strchr(message, ' ') + 1; - if (!strcmp(message, "stop")) - break; - printf("\033[01m%s\033[21m\n", message); - sleep(1); - @} - t (bus_poll_stop(&bus)); - bus_close(&bus); - return 0; - -fail: - perror("poll"); - bus_poll_stop(&bus); - bus_close(&bus); - return 1; -@} -@end example - - -@subsubheading @file{./write.c} -@example -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static char message[BUS_MEMORY_SIZE]; - -int main(int argc, char *argv[]) -@{ - bus_t bus; - struct timespec timeout; - if (argc < 2) @{ - fprintf(stderr, "%s: USAGE: %s message\n", argv[0], argv[0]); - return 2; - @} - sprintf(message, "0 %s", argv[1]); - t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); - t (clock_gettime(CLOCK_MONOTONIC, &timeout)); - timeout.tv_nsec += 100000000L; - t (bus_write_timed(&bus, message, &timeout, CLOCK_MONOTONIC)); - bus_close(&bus); - return 0; - -fail: - perror("write"); - bus_close(&bus); - return 1; -@} -@end example - - - -@node Nonblocking -@section Nonblocking - -This example shows how to use bus_poll instead of bus_read, -and how to do non-blocking polling and non-blocking writing. - -First of, run make to build this example. - -To start the example run @command{./init}. When you are done -run @command{./cleanup}. - -Running instances of @command{./poll} will check every second -if there is a new inbound message. Between these checks -@command{./write} will wait for all @command{./poll}:s to -receive the message. This means that @command{./write} blocks -while @command{./poll} sleeps. If two or more instances of -@command{./write} is started at approximately the same time, -only one will continue to write a message on the bus, the -others will fail. - -@command{./poll} will stop if the message ``stop'' is -broadcasted. - -@command{./poll}, @command{./init} and @command{./cleanup} -are run without any additional arguments. @command{./write} -is run with the message as the second argument. - - -@subsubheading @file{./Makefile} -@example -COMMANDS = init cleanup write poll - -all: $@{COMMANDS@} -%: %.c - $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< -clean: - -rm $@{COMMANDS@} - -.PHONY: all clean -@end example - - -@subsubheading @file{./cleanup.c} -@example -#include -#include - -int main() -@{ - return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); -@} -@end example - - -@subsubheading @file{./init.c} -@example -#include -#include - -int main() -@{ - return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); -@} -@end example - - -@subsubheading @file{./poll.c} -@example -#include -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -int main() -@{ - bus_t bus; - const char *message; - long long tick = 0; - t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); - t (bus_poll_start(&bus)); - for (;;) @{ - message = bus_poll(&bus, BUS_NOWAIT); - if (message == NULL) @{ - t (errno != EAGAIN); - printf("waiting... %lli\n", ++tick); - sleep(1); - continue; - @} - tick = 0; - message = strchr(message, ' ') + 1; - if (!strcmp(message, "stop")) - break; - printf("\033[01m%s\033[21m\n", message); - @} - t (bus_poll_stop(&bus)); - bus_close(&bus); - return 0; - -fail: - perror("poll"); - bus_poll_stop(&bus); - bus_close(&bus); - return 1; -@} -@end example - - -@subsubheading @file{./write.c} -@example -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static char message[BUS_MEMORY_SIZE]; - -int main(int argc, char *argv[]) -@{ - bus_t bus; - if (argc < 2) @{ - fprintf(stderr, "%s: USAGE: %s message\n", argv[0], argv[0]); - return 2; - @} - sprintf(message, "0 %s", argv[1]); - t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); - t (bus_write(&bus, message, BUS_NOWAIT)); - bus_close(&bus); - return 0; - -fail: - perror("write"); - bus_close(&bus); - return 1; -@} -@end example - - - -@node Daemon-dependencies -@section Daemon-dependencies - -This example shows how bus can be used in an init system -to provide ``aggressivly'' parallel startup of daemons. - -First of, run make to build this example. - -To start the example run @command{./init}. It will print in -red export-statement you may want to run i other terminals. -You will need to select at least one daemon, for example -you can run @code{./init d-ntp}. The available pretend -daemons are: @command{d-network}, @command{d-ntp}, and -@command{d-ssh}. - -When you are done run @command{./cleanup} with @env{BUS_INIT} -exported with the value printed by @command{./init}. - - -@subsubheading @file{./Makefile} -@example -COMMANDS = announce await-ready await-started cleanup \ - init require start-daemon test-daemon - -all: $@{COMMANDS@} -%: %.c - $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< -clean: - -rm $@{COMMANDS@} - -rm -r run - -.PHONY: all clean -@end example - - -@subsubheading @file{./announce.c} -@example -#include -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static char arg[4098]; - -int main(int argc, char *argv[]) -@{ - bus_t bus; - if (argc < 3) - return fprintf(stderr, "USAGE: %s state daemon", *argv), 2; - t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); - sprintf(arg, "%ji %s %s", (intmax_t)getppid(), argv[1], argv[2]); - t (bus_write(&bus, arg, 0)); - t (bus_close(&bus)); - return 0; - -fail: - perror("announce"); - return 1; -@} -@end example - - -@subsubheading @file{./await-ready.c} -@example -#include -#include -#include -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static char arg[4098]; -static int argc; -static char **argv; -static int remaining = 0; -static char *started = NULL; -static char msg[BUS_MEMORY_SIZE]; - -static void announce_wait(pid_t pid) -@{ - bus_t bus; - int i; - t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); - for (i = 1; i < argc; i++) @{ - if (!started[i]) @{ - sprintf(arg, "%ji awaiting-ready %s", (intmax_t)pid, argv[i]); - t (bus_write(&bus, arg, 0)); - @} - @} - t (bus_close(&bus)); - return; - -fail: - perror("await-ready"); -@} - -static int callback(const char *message, void *user_data) -@{ - int i; - char *arg2; - char *arg3; - pid_t pid; - pid_t ppid; - - if (!message) @{ - ppid = getppid(); - pid = fork(); - if (pid == 0) @{ - if (fork() == 0) - announce_wait(ppid); - exit(0); - @} else @{ - (void) waitpid(pid, NULL, 0); - /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ - @} - return 1; - @} - - strncpy(msg, message, BUS_MEMORY_SIZE - 1); - msg[BUS_MEMORY_SIZE - 1] = 0; - - arg2 = strchr(msg, ' '); - if (!arg2) - return 1; - arg3 = strchr(++arg2, ' '); - if (!arg3) - return 1; - *arg3++ = 0; - - if (strcmp(arg2, "ready")) - return 1; - - for (i = 1; i < argc; i++) - if (!started[i] && !strcmp(argv[i], arg3)) - started[i] = 1, remaining--; - - return !!remaining; - (void) user_data; -@} - -int main(int argc_, char *argv_[]) -@{ - bus_t bus; - int i; - - argc = argc_; - argv = argv_; - - if (argc < 2) - return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; - t (bus_open(&bus, getenv("BUS_INIT"), BUS_RDONLY)); - started = calloc(argc, sizeof(char)); - t (started == NULL); - - started[0] = 1; - for (i = 1; i < argc; i++) @{ - sprintf(arg, "grep '^%s$'" - " <\"$@{XDG_RUNTIME_DIR@}/ready-daemons\"" - " >/dev/null", - argv[i]); - if (!WEXITSTATUS(system(arg))) - started[i] = 1; - else - remaining++; - @} - - if (remaining) - bus_read(&bus, callback, NULL); - - bus_close(&bus); - free(started); - return 0; - -fail: - perror("await-ready"); - bus_close(&bus); - free(started); - return 1; -@} -@end example - - -@subsubheading @file{./await-started.c} -@example -#include -#include -#include -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static char arg[4098]; -static int argc; -static char **argv; -static int remaining = 0; -static char *started = NULL; -static char msg[BUS_MEMORY_SIZE]; - -static void announce_wait(pid_t pid) -@{ - bus_t bus; - int i; - t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); - for (i = 1; i < argc; i++) @{ - if (!started[i]) @{ - sprintf(arg, "%ji awaiting-started %s", (intmax_t)pid, argv[i]); - t (bus_write(&bus, arg, 0)); - @} - @} - t (bus_close(&bus)); - return; - -fail: - perror("await-started"); -@} - -static int callback(const char *message, void *user_data) -@{ - int i; - char *arg2; - char *arg3; - pid_t pid; - pid_t ppid; - - if (!message) @{ - ppid = getppid(); - pid = fork(); - if (pid == 0) @{ - if (fork() == 0) - announce_wait(ppid); - exit(0); - @} else @{ - (void) waitpid(pid, NULL, 0); - /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ - @} - return 1; - @} - - strncpy(msg, message, BUS_MEMORY_SIZE - 1); - msg[BUS_MEMORY_SIZE - 1] = 0; - - arg2 = strchr(msg, ' '); - if (!arg2) - return 1; - arg3 = strchr(++arg2, ' '); - if (!arg3) - return 1; - *arg3++ = 0; - - if (strcmp(arg2, "started") && strcmp(arg2, "ready")) - return 1; - - for (i = 1; i < argc; i++) - if (!started[i] && !strcmp(argv[i], arg3)) - started[i] = 1, remaining--; - - return !!remaining; - (void) user_data; -@} - -int main(int argc_, char *argv_[]) -@{ - bus_t bus; - int i; - - argc = argc_; - argv = argv_; - - if (argc < 2) - return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; - t (bus_open(&bus, getenv("BUS_INIT"), BUS_RDONLY)); - started = calloc(argc, sizeof(char)); - t (started == NULL); - - started[0] = 1; - for (i = 1; i < argc; i++) @{ - sprintf(arg, "grep '^%s$'" - " <\"$@{XDG_RUNTIME_DIR@}/started-daemons\"" - " >/dev/null", - argv[i]); - if (!WEXITSTATUS(system(arg))) @{ - started[i] = 1; - @} else @{ - sprintf(arg, "grep '^%s$'" - " <\"$@{XDG_RUNTIME_DIR@}/ready-daemons\"" - " >/dev/null", - argv[i]); - if (!WEXITSTATUS(system(arg))) - started[i] = 1; - else - remaining++; - @} - @} - - if (remaining) - bus_read(&bus, callback, NULL); - - bus_close(&bus); - free(started); - return 0; - -fail: - perror("await-started"); - bus_close(&bus); - free(started); - return 1; -@} -@end example - - -@subsubheading @file{./cleanup.c} -@example -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -int main() -@{ - char *bus_address = getenv("BUS_INIT"); - if (!bus_address || !*bus_address) @{ - fprintf(stderr, "$BUS_INIT has not been set, its export statement " - "should have been printed in bold red by ./init\n"); - return 1; - @} - t (bus_unlink(bus_address)); - return 0; - -fail: - perror("cleanup"); - return 1; -@} -@end example - - -@subsubheading @file{./d-network} -@example -#!/bin/sh -PATH=.:$PATH -d=d-network - -echo $d: starting -sleep 2 -echo $d: ready -announce ready $d -@end example - - -@subsubheading @file{./d-ntp} -@example -#!/bin/sh -PATH=.:$PATH -d=d-ntp - -require d-network -echo $d: started -announce started $d -await-ready d-network -echo $d: ready -announce ready $d -@end example - - -@subsubheading @file{./d-ssh} -@example -#!/bin/sh -PATH=.:$PATH -d=d-ssh - -require d-network -echo $d: starting -sleep 1 -echo $d: started -announce started $d -sleep 1 -echo $d: ready -announce ready $d -@end example - - -@subsubheading @file{./init.c} -@example -#include -#include -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail -#define _2(...) __VA_ARGS__, __VA_ARGS__ - -static char msg[BUS_MEMORY_SIZE]; -static int argc; -static char **argv; -static char arg[4098]; - -static void start_daemons() -@{ - int i; - for (i = 1; i < argc; i++) - if (fork() == 0) - execl("./start-daemon", "./start-daemon", argv[i], NULL); -@} - -static int callback(const char *message, void *user_data) -@{ - pid_t pid; - char *arg2; - char *arg3; - if (!message) @{ - pid = fork(); - t (pid == -1); - if (pid == 0) @{ - if (fork() == 0) @{ - start_daemons(); - @} - exit(0); - @} else @{ - (void) waitpid(pid, NULL, 0); - /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ - @} - return 1; - @} - - strncpy(msg, message, BUS_MEMORY_SIZE - 1); - msg[BUS_MEMORY_SIZE - 1] = 0; - - pid = fork(); - t (pid == -1); - - if (pid == 0) @{ - if (fork() == 0) @{ - arg2 = strchr(msg, ' '); - if (arg2 == NULL) - exit(0); - arg3 = strchr(++arg2, ' '); - if (arg3 == NULL) - exit(0); - *arg3++ = 0; - if (!strcmp(arg2, "require")) @{ - execl(_2("./start-daemon"), arg3, NULL); - @} else if (!strcmp(arg2, "awaiting-started")) @{ - execl(_2("./test-daemon"), arg3, "started", NULL); - @} else if (!strcmp(arg2, "awaiting-ready") || - !strcmp(arg2, "awaiting")) @{ - execl(_2("./test-daemon"), arg3, "ready", NULL); - @} else if (!strcmp(arg2, "started")) @{ - sprintf(arg, - "grep '^%s\\$' < \"%s\" >/dev/null || echo %s >> \"%s\"", - _2(arg3, "$@{XDG_RUNTIME_DIR@}/started-daemons")); - execlp(_2("sh"), "-c", arg, NULL); - @} else if (!strcmp(arg2, "ready")) @{ - sprintf(arg, - "grep '^%s\\$' < \"%s\" >/dev/null || echo %s >> \"%s\"", - _2(arg3, "$@{XDG_RUNTIME_DIR@}/ready-daemons")); - execlp(_2("sh"), "-c", arg, NULL); - @} - @} - exit(0); - @} else @{ - (void) waitpid(pid, NULL, 0); - /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ - @} - - return 1; - (void) user_data; - -fail: - perror("init"); - return -1; -@} - -int main(int argc_, char *argv_[]) -@{ - char *bus_address = NULL; - bus_t bus; - argv = argv_; - argc = argc_; - if (argc < 2) @{ - fprintf(stderr, "USAGE: %s daemon...\n", *argv); - return 1; - @} - t (setenv("XDG_RUNTIME_DIR", "./run", 1)); - /* @w{@xrm{}Real init systems with not have the period.@xtt{}} */ - system("mkdir -p -- \"$@{XDG_RUNTIME_DIR@}\""); - system("truncate -s 0 -- \"$@{XDG_RUNTIME_DIR@}/started-daemons\""); - system("truncate -s 0 -- \"$@{XDG_RUNTIME_DIR@}/ready-daemons\""); - t (bus_create(NULL, 1, &bus_address)); - fprintf(stderr, "\033[00;01;31mexport BUS_INIT=%s\033[00m\n", - bus_address); - fprintf(stderr, "\033[00;31mexport XDG_RUNTIME_DIR=./run\033[00m\n"); - t (setenv("BUS_INIT", bus_address, 1)); - t (bus_open(&bus, bus_address, BUS_RDONLY)); - t (bus_read(&bus, callback, NULL)); - bus_close(&bus); - free(bus_address); - return 0; - -fail: - perror("init"); - bus_close(&bus); - free(bus_address); - return 1; -@} -@end example - - -@subsubheading @file{./require.c} -@example -#include -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static char arg[4098]; - -int main(int argc, char *argv[]) -@{ - bus_t bus; - int i; - if (argc < 2) - return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; - t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); - - for (i = 1; i < argc; i++) @{ - sprintf(arg, "grep '^%s$' <\"%s\" >/dev/null", - argv[i], "$@{XDG_RUNTIME_DIR@}/started-daemons"); - if (WEXITSTATUS(system(arg))) @{ - sprintf(arg, "%ji require %s", (intmax_t)getppid(), argv[i]); - t (bus_write(&bus, arg, 0)); - @} - @} - - bus_close(&bus); - return 0; - -fail: - perror("require"); - bus_close(&bus); - return 1; -@} -@end example - - -@subsubheading @file{./start-daemon.c} -@example -#include -#include -#include -#include -#include -#include - -static char arg[4098]; - -int main(int argc, char *argv[]) -@{ - if (argc != 2) @{ - fprintf(stderr, "This program should be called from ./init\n"); - return 2; - @} - - sprintf(arg, "grep '^%s$' <\"%s\" >/dev/null", - argv[1], "$@{XDG_RUNTIME_DIR@}/started-daemons"); - if (!WEXITSTATUS(system(arg))) - return 0; - sprintf(arg, "grep '^%s$' <\"%s\" >/dev/null", - argv[1], "$@{XDG_RUNTIME_DIR@}/ready-daemons"); - if (!WEXITSTATUS(system(arg))) - return 0; - - sprintf(arg, "./%s", argv[1]); - execlp(arg, arg, NULL); - perror("start-daemon"); - return 1; -@} -@end example - - -@subsubheading @file{./test-daemon.c} -@example -#include -#include -#include -#include - -#define t(stmt) if (stmt) goto fail - -static char arg[4098]; - -int main(int argc, char *argv[]) -@{ - bus_t bus; - if (argc != 3) @{ - fprintf(stderr, "This program should be called from ./init\n"); - return 2; - @} - -retry: - sprintf(arg, "grep '^%s$'" - " <\"$@{XDG_RUNTIME_DIR@}/%s-daemons\"" - " >/dev/null", - argv[1], argv[2]); - if (!WEXITSTATUS(system(arg))) @{ - t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); - sprintf(arg, "0 %s %s", argv[2], argv[1]); - t (bus_write(&bus, arg, 0)); - bus_close(&bus); - @} else if (!strcmp(argv[2], "started")) @{ - argv[2] = "ready"; - goto retry; - @} - return 0; - -fail: - perror("test-daemon"); - return 1; -@} -@end example - - - -@node GNU Free Documentation License -@appendix GNU Free Documentation License -@include fdl.texinfo - -@bye - diff --git a/doc/info/fdl.texinfo b/doc/info/fdl.texinfo deleted file mode 100644 index cb71f05..0000000 --- a/doc/info/fdl.texinfo +++ /dev/null @@ -1,505 +0,0 @@ -@c The GNU Free Documentation License. -@center Version 1.3, 3 November 2008 - -@c This file is intended to be included within another document, -@c hence no sectioning command or @node. - -@display -Copyright @copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. -@uref{http://fsf.org/} - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. -@end display - -@enumerate 0 -@item -PREAMBLE - -The purpose of this License is to make a manual, textbook, or other -functional and useful document @dfn{free} in the sense of freedom: to -assure everyone the effective freedom to copy and redistribute it, -with or without modifying it, either commercially or noncommercially. -Secondarily, this License preserves for the author and publisher a way -to get credit for their work, while not being considered responsible -for modifications made by others. - -This License is a kind of ``copyleft'', which means that derivative -works of the document must themselves be free in the same sense. It -complements the GNU General Public License, which is a copyleft -license designed for free software. - -We have designed this License in order to use it for manuals for free -software, because free software needs free documentation: a free -program should come with manuals providing the same freedoms that the -software does. But this License is not limited to software manuals; -it can be used for any textual work, regardless of subject matter or -whether it is published as a printed book. We recommend this License -principally for works whose purpose is instruction or reference. - -@item -APPLICABILITY AND DEFINITIONS - -This License applies to any manual or other work, in any medium, that -contains a notice placed by the copyright holder saying it can be -distributed under the terms of this License. Such a notice grants a -world-wide, royalty-free license, unlimited in duration, to use that -work under the conditions stated herein. The ``Document'', below, -refers to any such manual or work. Any member of the public is a -licensee, and is addressed as ``you''. You accept the license if you -copy, modify or distribute the work in a way requiring permission -under copyright law. - -A ``Modified Version'' of the Document means any work containing the -Document or a portion of it, either copied verbatim, or with -modifications and/or translated into another language. - -A ``Secondary Section'' is a named appendix or a front-matter section -of the Document that deals exclusively with the relationship of the -publishers or authors of the Document to the Document's overall -subject (or to related matters) and contains nothing that could fall -directly within that overall subject. (Thus, if the Document is in -part a textbook of mathematics, a Secondary Section may not explain -any mathematics.) The relationship could be a matter of historical -connection with the subject or with related matters, or of legal, -commercial, philosophical, ethical or political position regarding -them. - -The ``Invariant Sections'' are certain Secondary Sections whose titles -are designated, as being those of Invariant Sections, in the notice -that says that the Document is released under this License. If a -section does not fit the above definition of Secondary then it is not -allowed to be designated as Invariant. The Document may contain zero -Invariant Sections. If the Document does not identify any Invariant -Sections then there are none. - -The ``Cover Texts'' are certain short passages of text that are listed, -as Front-Cover Texts or Back-Cover Texts, in the notice that says that -the Document is released under this License. A Front-Cover Text may -be at most 5 words, and a Back-Cover Text may be at most 25 words. - -A ``Transparent'' copy of the Document means a machine-readable copy, -represented in a format whose specification is available to the -general public, that is suitable for revising the document -straightforwardly with generic text editors or (for images composed of -pixels) generic paint programs or (for drawings) some widely available -drawing editor, and that is suitable for input to text formatters or -for automatic translation to a variety of formats suitable for input -to text formatters. A copy made in an otherwise Transparent file -format whose markup, or absence of markup, has been arranged to thwart -or discourage subsequent modification by readers is not Transparent. -An image format is not Transparent if used for any substantial amount -of text. A copy that is not ``Transparent'' is called ``Opaque''. - -Examples of suitable formats for Transparent copies include plain -ASCII without markup, Texinfo input format, La@TeX{} input -format, SGML or XML using a publicly available -DTD, and standard-conforming simple HTML, -PostScript or PDF designed for human modification. Examples -of transparent image formats include PNG, XCF and -JPG. Opaque formats include proprietary formats that can be -read and edited only by proprietary word processors, SGML or -XML for which the DTD and/or processing tools are -not generally available, and the machine-generated HTML, -PostScript or PDF produced by some word processors for -output purposes only. - -The ``Title Page'' means, for a printed book, the title page itself, -plus such following pages as are needed to hold, legibly, the material -this License requires to appear in the title page. For works in -formats which do not have any title page as such, ``Title Page'' means -the text near the most prominent appearance of the work's title, -preceding the beginning of the body of the text. - -The ``publisher'' means any person or entity that distributes copies -of the Document to the public. - -A section ``Entitled XYZ'' means a named subunit of the Document whose -title either is precisely XYZ or contains XYZ in parentheses following -text that translates XYZ in another language. (Here XYZ stands for a -specific section name mentioned below, such as ``Acknowledgements'', -``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title'' -of such a section when you modify the Document means that it remains a -section ``Entitled XYZ'' according to this definition. - -The Document may include Warranty Disclaimers next to the notice which -states that this License applies to the Document. These Warranty -Disclaimers are considered to be included by reference in this -License, but only as regards disclaiming warranties: any other -implication that these Warranty Disclaimers may have is void and has -no effect on the meaning of this License. - -@item -VERBATIM COPYING - -You may copy and distribute the Document in any medium, either -commercially or noncommercially, provided that this License, the -copyright notices, and the license notice saying this License applies -to the Document are reproduced in all copies, and that you add no other -conditions whatsoever to those of this License. You may not use -technical measures to obstruct or control the reading or further -copying of the copies you make or distribute. However, you may accept -compensation in exchange for copies. If you distribute a large enough -number of copies you must also follow the conditions in section 3. - -You may also lend copies, under the same conditions stated above, and -you may publicly display copies. - -@item -COPYING IN QUANTITY - -If you publish printed copies (or copies in media that commonly have -printed covers) of the Document, numbering more than 100, and the -Document's license notice requires Cover Texts, you must enclose the -copies in covers that carry, clearly and legibly, all these Cover -Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on -the back cover. Both covers must also clearly and legibly identify -you as the publisher of these copies. The front cover must present -the full title with all words of the title equally prominent and -visible. You may add other material on the covers in addition. -Copying with changes limited to the covers, as long as they preserve -the title of the Document and satisfy these conditions, can be treated -as verbatim copying in other respects. - -If the required texts for either cover are too voluminous to fit -legibly, you should put the first ones listed (as many as fit -reasonably) on the actual cover, and continue the rest onto adjacent -pages. - -If you publish or distribute Opaque copies of the Document numbering -more than 100, you must either include a machine-readable Transparent -copy along with each Opaque copy, or state in or with each Opaque copy -a computer-network location from which the general network-using -public has access to download using public-standard network protocols -a complete Transparent copy of the Document, free of added material. -If you use the latter option, you must take reasonably prudent steps, -when you begin distribution of Opaque copies in quantity, to ensure -that this Transparent copy will remain thus accessible at the stated -location until at least one year after the last time you distribute an -Opaque copy (directly or through your agents or retailers) of that -edition to the public. - -It is requested, but not required, that you contact the authors of the -Document well before redistributing any large number of copies, to give -them a chance to provide you with an updated version of the Document. - -@item -MODIFICATIONS - -You may copy and distribute a Modified Version of the Document under -the conditions of sections 2 and 3 above, provided that you release -the Modified Version under precisely this License, with the Modified -Version filling the role of the Document, thus licensing distribution -and modification of the Modified Version to whoever possesses a copy -of it. In addition, you must do these things in the Modified Version: - -@enumerate A -@item -Use in the Title Page (and on the covers, if any) a title distinct -from that of the Document, and from those of previous versions -(which should, if there were any, be listed in the History section -of the Document). You may use the same title as a previous version -if the original publisher of that version gives permission. - -@item -List on the Title Page, as authors, one or more persons or entities -responsible for authorship of the modifications in the Modified -Version, together with at least five of the principal authors of the -Document (all of its principal authors, if it has fewer than five), -unless they release you from this requirement. - -@item -State on the Title page the name of the publisher of the -Modified Version, as the publisher. - -@item -Preserve all the copyright notices of the Document. - -@item -Add an appropriate copyright notice for your modifications -adjacent to the other copyright notices. - -@item -Include, immediately after the copyright notices, a license notice -giving the public permission to use the Modified Version under the -terms of this License, in the form shown in the Addendum below. - -@item -Preserve in that license notice the full lists of Invariant Sections -and required Cover Texts given in the Document's license notice. - -@item -Include an unaltered copy of this License. - -@item -Preserve the section Entitled ``History'', Preserve its Title, and add -to it an item stating at least the title, year, new authors, and -publisher of the Modified Version as given on the Title Page. If -there is no section Entitled ``History'' in the Document, create one -stating the title, year, authors, and publisher of the Document as -given on its Title Page, then add an item describing the Modified -Version as stated in the previous sentence. - -@item -Preserve the network location, if any, given in the Document for -public access to a Transparent copy of the Document, and likewise -the network locations given in the Document for previous versions -it was based on. These may be placed in the ``History'' section. -You may omit a network location for a work that was published at -least four years before the Document itself, or if the original -publisher of the version it refers to gives permission. - -@item -For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve -the Title of the section, and preserve in the section all the -substance and tone of each of the contributor acknowledgements and/or -dedications given therein. - -@item -Preserve all the Invariant Sections of the Document, -unaltered in their text and in their titles. Section numbers -or the equivalent are not considered part of the section titles. - -@item -Delete any section Entitled ``Endorsements''. Such a section -may not be included in the Modified Version. - -@item -Do not retitle any existing section to be Entitled ``Endorsements'' or -to conflict in title with any Invariant Section. - -@item -Preserve any Warranty Disclaimers. -@end enumerate - -If the Modified Version includes new front-matter sections or -appendices that qualify as Secondary Sections and contain no material -copied from the Document, you may at your option designate some or all -of these sections as invariant. To do this, add their titles to the -list of Invariant Sections in the Modified Version's license notice. -These titles must be distinct from any other section titles. - -You may add a section Entitled ``Endorsements'', provided it contains -nothing but endorsements of your Modified Version by various -parties---for example, statements of peer review or that the text has -been approved by an organization as the authoritative definition of a -standard. - -You may add a passage of up to five words as a Front-Cover Text, and a -passage of up to 25 words as a Back-Cover Text, to the end of the list -of Cover Texts in the Modified Version. Only one passage of -Front-Cover Text and one of Back-Cover Text may be added by (or -through arrangements made by) any one entity. If the Document already -includes a cover text for the same cover, previously added by you or -by arrangement made by the same entity you are acting on behalf of, -you may not add another; but you may replace the old one, on explicit -permission from the previous publisher that added the old one. - -The author(s) and publisher(s) of the Document do not by this License -give permission to use their names for publicity for or to assert or -imply endorsement of any Modified Version. - -@item -COMBINING DOCUMENTS - -You may combine the Document with other documents released under this -License, under the terms defined in section 4 above for modified -versions, provided that you include in the combination all of the -Invariant Sections of all of the original documents, unmodified, and -list them all as Invariant Sections of your combined work in its -license notice, and that you preserve all their Warranty Disclaimers. - -The combined work need only contain one copy of this License, and -multiple identical Invariant Sections may be replaced with a single -copy. If there are multiple Invariant Sections with the same name but -different contents, make the title of each such section unique by -adding at the end of it, in parentheses, the name of the original -author or publisher of that section if known, or else a unique number. -Make the same adjustment to the section titles in the list of -Invariant Sections in the license notice of the combined work. - -In the combination, you must combine any sections Entitled ``History'' -in the various original documents, forming one section Entitled -``History''; likewise combine any sections Entitled ``Acknowledgements'', -and any sections Entitled ``Dedications''. You must delete all -sections Entitled ``Endorsements.'' - -@item -COLLECTIONS OF DOCUMENTS - -You may make a collection consisting of the Document and other documents -released under this License, and replace the individual copies of this -License in the various documents with a single copy that is included in -the collection, provided that you follow the rules of this License for -verbatim copying of each of the documents in all other respects. - -You may extract a single document from such a collection, and distribute -it individually under this License, provided you insert a copy of this -License into the extracted document, and follow this License in all -other respects regarding verbatim copying of that document. - -@item -AGGREGATION WITH INDEPENDENT WORKS - -A compilation of the Document or its derivatives with other separate -and independent documents or works, in or on a volume of a storage or -distribution medium, is called an ``aggregate'' if the copyright -resulting from the compilation is not used to limit the legal rights -of the compilation's users beyond what the individual works permit. -When the Document is included in an aggregate, this License does not -apply to the other works in the aggregate which are not themselves -derivative works of the Document. - -If the Cover Text requirement of section 3 is applicable to these -copies of the Document, then if the Document is less than one half of -the entire aggregate, the Document's Cover Texts may be placed on -covers that bracket the Document within the aggregate, or the -electronic equivalent of covers if the Document is in electronic form. -Otherwise they must appear on printed covers that bracket the whole -aggregate. - -@item -TRANSLATION - -Translation is considered a kind of modification, so you may -distribute translations of the Document under the terms of section 4. -Replacing Invariant Sections with translations requires special -permission from their copyright holders, but you may include -translations of some or all Invariant Sections in addition to the -original versions of these Invariant Sections. You may include a -translation of this License, and all the license notices in the -Document, and any Warranty Disclaimers, provided that you also include -the original English version of this License and the original versions -of those notices and disclaimers. In case of a disagreement between -the translation and the original version of this License or a notice -or disclaimer, the original version will prevail. - -If a section in the Document is Entitled ``Acknowledgements'', -``Dedications'', or ``History'', the requirement (section 4) to Preserve -its Title (section 1) will typically require changing the actual -title. - -@item -TERMINATION - -You may not copy, modify, sublicense, or distribute the Document -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense, or distribute it is void, and -will automatically terminate your rights under this License. - -However, if you cease all violation of this License, then your license -from a particular copyright holder is reinstated (a) provisionally, -unless and until the copyright holder explicitly and finally -terminates your license, and (b) permanently, if the copyright holder -fails to notify you of the violation by some reasonable means prior to -60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, receipt of a copy of some or all of the same material does -not give you any rights to use it. - -@item -FUTURE REVISIONS OF THIS LICENSE - -The Free Software Foundation may publish new, revised versions -of the GNU Free Documentation License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. See -@uref{http://www.gnu.org/copyleft/}. - -Each version of the License is given a distinguishing version number. -If the Document specifies that a particular numbered version of this -License ``or any later version'' applies to it, you have the option of -following the terms and conditions either of that specified version or -of any later version that has been published (not as a draft) by the -Free Software Foundation. If the Document does not specify a version -number of this License, you may choose any version ever published (not -as a draft) by the Free Software Foundation. If the Document -specifies that a proxy can decide which future versions of this -License can be used, that proxy's public statement of acceptance of a -version permanently authorizes you to choose that version for the -Document. - -@item -RELICENSING - -``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any -World Wide Web server that publishes copyrightable works and also -provides prominent facilities for anybody to edit those works. A -public wiki that anybody can edit is an example of such a server. A -``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the -site means any set of copyrightable works thus published on the MMC -site. - -``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0 -license published by Creative Commons Corporation, a not-for-profit -corporation with a principal place of business in San Francisco, -California, as well as future copyleft versions of that license -published by that same organization. - -``Incorporate'' means to publish or republish a Document, in whole or -in part, as part of another Document. - -An MMC is ``eligible for relicensing'' if it is licensed under this -License, and if all works that were first published under this License -somewhere other than this MMC, and subsequently incorporated in whole -or in part into the MMC, (1) had no cover texts or invariant sections, -and (2) were thus incorporated prior to November 1, 2008. - -The operator of an MMC Site may republish an MMC contained in the site -under CC-BY-SA on the same site at any time before August 1, 2009, -provided the MMC is eligible for relicensing. - -@end enumerate - -@page -@heading ADDENDUM: How to use this License for your documents - -To use this License in a document you have written, include a copy of -the License in the document and put the following copyright and -license notices just after the title page: - -@smallexample -@group - Copyright (C) @var{year} @var{your name}. - Permission is granted to copy, distribute and/or modify this document - under the terms of the GNU Free Documentation License, Version 1.3 - or any later version published by the Free Software Foundation; - with no Invariant Sections, no Front-Cover Texts, and no Back-Cover - Texts. A copy of the license is included in the section entitled ``GNU - Free Documentation License''. -@end group -@end smallexample - -If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, -replace the ``with@dots{}Texts.''@: line with this: - -@smallexample -@group - with the Invariant Sections being @var{list their titles}, with - the Front-Cover Texts being @var{list}, and with the Back-Cover Texts - being @var{list}. -@end group -@end smallexample - -If you have Invariant Sections without Cover Texts, or some other -combination of the three, merge those two alternatives to suit the -situation. - -If your document contains nontrivial examples of program code, we -recommend releasing these examples in parallel under your choice of -free software license, such as the GNU General Public License, -to permit their use in free software. - -@c Local Variables: -@c ispell-local-pdict: "ispell-dict" -@c End: diff --git a/doc/man/bus-broadcast.1 b/doc/man/bus-broadcast.1 deleted file mode 100644 index a4dc76c..0000000 --- a/doc/man/bus-broadcast.1 +++ /dev/null @@ -1,35 +0,0 @@ -.TH BUS-BROADCAST 1 BUS-%VERSION% -.SH NAME -bus broadcast - Broadcast a message on a bus -.SH SYNOPSIS -.B bus broadcast -.IR [options] -[--] -.IR pathname -.IR message -.SH DESCRIPTION -Broadcast \fImessage\fP on the bus associated with \fIpathname\fP. -.SH OPTIONS -.TP -.B \-n -Fail if another process is attempting to broadcast on the bus. -.SH EXIT STATUS -.TP -0 -The command was successful. -.TP -1 -The command failed. -.TP -2 -The command is not recognised. -.SH SEE ALSO -.BR bus (5) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus-chgrp.1 b/doc/man/bus-chgrp.1 deleted file mode 100644 index d825e32..0000000 --- a/doc/man/bus-chgrp.1 +++ /dev/null @@ -1,38 +0,0 @@ -.TH BUS-CHGRP 1 BUS-%VERSION% -.SH NAME -bus chgrp - Change group ownership of a bus -.SH SYNOPSIS -.B bus chgrp -[--] -.IR group -.IR pathname -.SH DESCRIPTION -Change the group, that owns a bus with an associated \fIpathname\fP, -to the specified \fIgroup\fP. The \fIgroup\fP can be specified either -with a GID or with a group name. -.PP -The current ownership of a bus can be retrieved by running -.BR stat (1) -over the \fIpathname\fP of the bus. -.SH EXIT STATUS -.TP -0 -The command was successful. -.TP -1 -The command failed. -.TP -2 -The command is not recognised. -.SH SEE ALSO -.BR bus-chown (1), -.BR bus-chmod (1), -.BR stat (1) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus-chmod.1 b/doc/man/bus-chmod.1 deleted file mode 100644 index 3a44423..0000000 --- a/doc/man/bus-chmod.1 +++ /dev/null @@ -1,49 +0,0 @@ -.TH BUS-CHMOD 1 BUS-%VERSION% -.SH NAME -bus chmod - Change permissions on a bus -.SH SYNOPSIS -.B bus chmod -[--] -.IR permissions -.IR pathname -.SH DESCRIPTION -Change who have access to a bus with an associated \fIpathname\fP. -In the \fIpermissions\fP, the owner, the group, and others (not -in group) are represented by the symbols \fBu\fP, \fBg\fP, and -\fBo\fP, respectively. The \fIpermissions\fP string is imagined -to have always be prefixed with an \fB=\fP. This symbols means -that all user classes list after it, and only those classes, as -permission to use the bus. Similarly the symbols \fB+\fP and -\fB\-\fP can be used to grant and revoke access, respectively. -The symbols \fB=\fP, \fB+\fP, and \fB\-\fP can be mixed, and are -interpreted from left to right. Alternatively the \fIpermissions\fP -string can be a octal number, where the owner is represented by any -bit in 700 (100, 200, or 400, or any combination thereof), the -group is represented by any bit in 70, and others (not in the group) -is represented by any bit in 7. -.PP -The current permissions of a bus can be retrieved by running -.BR stat (1) -over the \fIpathname\fP of the bus. -.SH EXIT STATUS -.TP -0 -The command was successful. -.TP -1 -The command failed. -.TP -2 -The command is not recognised. -.SH SEE ALSO -.BR bus-chown (1), -.BR bus-chgrp (1), -.BR stat (1) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus-chown.1 b/doc/man/bus-chown.1 deleted file mode 100644 index 24487a1..0000000 --- a/doc/man/bus-chown.1 +++ /dev/null @@ -1,41 +0,0 @@ -.TH BUS-CHOWN 1 BUS-%VERSION% -.SH NAME -bus chown - Change ownership of a bus -.SH SYNOPSIS -.B bus chown -[--] -.IR owner[:group] -.IR pathname -.SH DESCRIPTION -Change the owner, that owns a bus with an associated \fIpathname\fP, -to the specified \fIowner\fP. The \fIowner\fP can be specified either -with a UID or with a user name. If a \fIgroup\fP is specified, the -bus's owner-group will be set to that \fIgroup\fP, otherwise the group -will remain unchanged. The \fIgroup\fP can be specified either with -a GID or with a group name. -.PP -The current ownership of a bus can be retrieved by running -.BR stat (1) -over the \fIpathname\fP of the bus. -.SH EXIT STATUS -.TP -0 -The command was successful. -.TP -1 -The command failed. -.TP -2 -The command is not recognised. -.SH SEE ALSO -.BR bus-chgrp (1), -.BR bus-chmod (1), -.BR stat (1) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus-create.1 b/doc/man/bus-create.1 deleted file mode 100644 index 91041cf..0000000 --- a/doc/man/bus-create.1 +++ /dev/null @@ -1,37 +0,0 @@ -.TH BUS-CREATE 1 BUS-%VERSION% -.SH NAME -bus create - Create a bus -.SH SYNOPSIS -.B bus create -.IR [options] -[--] -.IR [pathname] -.SH DESCRIPTION -Create a bus with an associated \fIpathname\fP. If \fIpathname\fP -is omitted, a random pathname in \fI$XDG_RUNTIME_DIR/bus\fP will be -created and printed to stdout. -.SH OPTIONS -.TP -.B \-x -Fail if the \fIpathname\fP already exists. -.SH EXIT STATUS -.TP -0 -The command was successful. -.TP -1 -The command failed. -.TP -2 -The command is not recognised. -.SH SEE ALSO -.BR bus (5), -.BR bus-remove (1) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus-listen.1 b/doc/man/bus-listen.1 deleted file mode 100644 index 4977ce5..0000000 --- a/doc/man/bus-listen.1 +++ /dev/null @@ -1,32 +0,0 @@ -.TH BUS-LISTEN 1 BUS-%VERSION% -.SH NAME -bus listen - Listen for new messages on a bus -.SH SYNOPSIS -.B bus listen -[--] -.IR pathname -.IR command -.SH DESCRIPTION -Listen for new messages on the bus associated with \fIpathname\fP. Once -a message is received, \fIcommand\fP will be spawned with \fI$msg\fP set -to the received message. POSIX shell syntax applies to \fIcommand\fP. -.SH EXIT STATUS -.TP -0 -The command was successful. -.TP -1 -The command failed. -.TP -2 -The command is not recognised. -.SH SEE ALSO -.BR bus (5) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus-remove.1 b/doc/man/bus-remove.1 deleted file mode 100644 index 95e805c..0000000 --- a/doc/man/bus-remove.1 +++ /dev/null @@ -1,29 +0,0 @@ -.TH BUS-REMOVE 1 BUS-%VERSION% -.SH NAME -bus remove - Remove a bus -.SH SYNOPSIS -.B bus remove -[--] -.IR pathname -.SH DESCRIPTION -Remove the bus associated with \fIpathname\fP. -.SH EXIT STATUS -.TP -0 -The command was successful. -.TP -1 -The command failed. -.TP -2 -The command is not recognised. -.SH SEE ALSO -.BR bus (5) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus-wait.1 b/doc/man/bus-wait.1 deleted file mode 100644 index 4231cd0..0000000 --- a/doc/man/bus-wait.1 +++ /dev/null @@ -1,33 +0,0 @@ -.TH BUS-WAIT 1 BUS-%VERSION% -.SH NAME -bus wait - Listen for a new message on a bus -.SH SYNOPSIS -.B bus wait -[--] -.IR pathname -.IR command -.SH DESCRIPTION -Listen for a new message on the bus associated with \fIpathname\fP, stop -listening once a message has been received. Once a message is received, -\fIcommand\fP will be spawned with \fI$msg\fP set to the received -message. POSIX shell syntax applies to \fIcommand\fP. -.SH EXIT STATUS -.TP -0 -The command was successful. -.TP -1 -The command failed. -.TP -2 -The command is not recognised. -.SH SEE ALSO -.BR bus (5) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus.1 b/doc/man/bus.1 deleted file mode 100644 index afb3925..0000000 --- a/doc/man/bus.1 +++ /dev/null @@ -1,77 +0,0 @@ -.TH BUS 1 BUS-%VERSION% -.SH NAME -bus - A simple daemonless system for broadcasting messages locally -.SH SYNOPSIS -.B bus -.IR command -.IR argument\ ... -.SH COMMANDS -.TP -.B create -Create a bus, see -.BR bus-create (1) -for further details. -.TP -.B remove -Remove a bus, see -.BR bus-remove (1) -for further details. -.TP -.B listen -Listen for new message on a bus, see -.BR bus-listen (1) -for further details. -.TP -.B wait -Listen for one new message only on a bus, see -.BR bus-wait (1) -for further details. -.TP -.B broadcast -Broadcast a message on a bus, see -.BR bus-broadcast (1) -for further details. -.TP -.B chmod -Change permissions on a bus, see -.BR bus-chmod (1) -for further details. -.TP -.B chown -Change ownership of a bus, see -.BR bus-chown (1) -for further details. -.TP -.B chgrp -Change group ownership of a bus, see -.BR bus-chgrp (1) -for further details. -.SH EXIT STATUS -.TP -0 -The command was successful. -.TP -1 -The command failed. -.TP -2 -The command is not recognised. -.SH SEE ALSO -.BR bus-create (1), -.BR bus-remove (1), -.BR bus-listen (1), -.BR bus-wait (1), -.BR bus-broadcast (1), -.BR bus-chmod (1), -.BR bus-chown (1), -.BR bus-chgrp (1), -.BR bus (5), -.BR libbus (7) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus.5 b/doc/man/bus.5 deleted file mode 100644 index 0a5de7e..0000000 --- a/doc/man/bus.5 +++ /dev/null @@ -1,50 +0,0 @@ -.TH BUS 5 BUS-%VERSION% -.SH NAME -bus - A simple daemonless system for broadcasting messages locally -.SH DESCRIPTION -\fBbus\fP is a simple interprocess communication system for broadcasting -messages to other processes on the same machine. \fBbus\fP does not use -any daemon. Instead, all communication and synchronisation is managed -using System V (XSI) semaphores and System V (XSI) shared memory. -.PP -The command \fBbus create\fP can be used to create new buses. By -convention, buses should be stored in \fI$XDG_RUNTIME_DIR/bus\fP, this is -what \fBbus create\fP does if no pathname is given. The pathname of the -bus should be tracked using \fIBUS_X\fP, where \fIX\fP is replaced with -either: -.TP -.B GENERIC -For the bus used in generic cases. That is all but the cases of the -buses listed below. -.TP -.B AUDIO -For the bus used in with the audio subsystem is involved. -.TP -.B VIDEO -For the bus used in with the video subsystem is involved. -.TP -.B INPUT -For the bus used in with the input subsystem is involved. -.TP -.B FILES -For the bus used in with the storage subsystem is involved. -.PP -Messages broadcasted on a bus cannot be longer than 2047 bytes, -excluding NULL termination. Message should be encoded in UTF-8, -and most not contain the NULL character. -.PP -Broadcasted message should start with the process ID, or 0 if ephemeral, -whence the message originated, followed by a single regular space. -.SH SEE ALSO -.BR bus (1), -.BR libbus (7), -.BR semop (2), -.BR shmop (2) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus_chmod.3 b/doc/man/bus_chmod.3 deleted file mode 100644 index c2ac3f9..0000000 --- a/doc/man/bus_chmod.3 +++ /dev/null @@ -1,64 +0,0 @@ -.TH BUS_CHMOD 3 BUS-%VERSION% -.SH NAME -bus_chmod - Change bus mode bits -.SH SYNOPSIS -.LP -.nf -#include -.P -int bus_chmod(const char *\fIfile\fP, mode_t \fImode\fP); -.fi -.SH DESCRIPTION -The -.BR bus_chmod () -function gives access to the bus associated with \fIfile\fP -according to the following rules: -.TP -* -If (\fImode\fP &S_IRWXU) the owner should be given full access to the -bus. Otherwise the owner should have no access. -.TP -* -If (\fImode\fP &S_IRWXG) the group should be given read and write -access to the bus. Otherwise the group should have no access. -.TP -* -If (\fImode\fP &S_IRWXO) others (except the group) should be given -read and write access to the bus. Otherwise others should have no -access. -.PP -The current permissions of a bus can be retrieved by running -.BR stat (3) -over the \fIpathname\fP of the bus. -.SH RETURN VALUES -Upon successful completion, the function returns 0. Otherwise the -function returns -1 and sets \fIerrno\fP to indicate the error. -.SH ERRORS -The -.BR bus_chown (3) -function may fail and set \fIerrno\fP to any of the -errors specified for -.BR bus_open (3), -.BR chmod (3), -.BR semget (3), -.BR shmget (3) -and -.BR shmctl (3) -as well as any errors specified for the \fIIPC_STAT\fP and -\fIIPC_SET\fP commands for -.BR semctl (3). -.SH SEE ALSO -.BR bus-create (1), -.BR bus (5), -.BR libbus (7), -.BR bus_open (3), -.BR bus_read (3), -.BR stat (3) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus_chown.3 b/doc/man/bus_chown.3 deleted file mode 100644 index c047d83..0000000 --- a/doc/man/bus_chown.3 +++ /dev/null @@ -1,51 +0,0 @@ -.TH BUS_CHOWN 3 BUS-%VERSION% -.SH NAME -bus_chown - Change bus owner and group -.SH SYNOPSIS -.LP -.nf -#include -.P -int bus_chown(const char *\fIfile\fP, uid_t \fIowner\fP, gid_t \fIgroup\fP); -.fi -.SH DESCRIPTION -The -.BR bus_chown () -function changes the owner and the group of the bus associated with -\fIfile\fP to the \fIowner\fP and \fIgroup\fP, respectively. -.PP -The current ownership of a bus can be retrieved by running -.BR stat (3) -over the \fIpathname\fP of the bus. -.SH RETURN VALUES -Upon successful completion, the function returns 0. Otherwise the -function returns -1 and sets \fIerrno\fP to indicate the error. -.SH ERRORS -The -.BR bus_chown (3) -function may fail and set \fIerrno\fP to any of the -errors specified for -.BR bus_open (3), -.BR chown (3), -.BR semget (3), -.BR shmget (3) -and -.BR shmctl (3) -as well as any errors specified for the \fIIPC_STAT\fP and -\fIIPC_SET\fP commands for -.BR semctl (3). -.SH SEE ALSO -.BR bus-create (1), -.BR bus (5), -.BR libbus (7), -.BR bus_open (3), -.BR bus_read (3), -.BR stat (3) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus_close.3 b/doc/man/bus_close.3 deleted file mode 100644 index dc35e6b..0000000 --- a/doc/man/bus_close.3 +++ /dev/null @@ -1,36 +0,0 @@ -.TH BUS_CLOSE 3 BUS-%VERSION% -.SH NAME -bus_close - Close a bus -.SH SYNOPSIS -.LP -.nf -#include -.P -int bus_close(bus_t *\fIbus\fP); -.fi -.SH DESCRIPTION -The -.BR bus_close () -function disposes of resources allocated to the process, as referenced -in the \fIbus\fP argument. -.SH RETURN VALUES -Upon successful completion, the function returns 0. Otherwise the -function returns -1 and sets \fIerrno\fP to indicate the error. -.SH ERRORS -.TP -.B EINVAL -The bus does not exist. -.SH SEE ALSO -.BR bus-create (1), -.BR bus (5), -.BR libbus (7), -.BR bus_open (3), -.BR bus_unlink (3) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus_create.3 b/doc/man/bus_create.3 deleted file mode 100644 index 88e6efe..0000000 --- a/doc/man/bus_create.3 +++ /dev/null @@ -1,63 +0,0 @@ -.TH BUS_CREATE 3 BUS-%VERSION% -.SH NAME -bus_create - Create a new bus -.SH SYNOPSIS -.LP -.nf -#include -.P -int bus_create(const char *\fIfile\fP, int \fIflags\fP, char **\fIout_file\fP); -.fi -.SH DESCRIPTION -The -.BR bus_create () -function creates a bus with the asscoiated pathname specifed by the -value of the parameter \fIfile\fP. If \fIfile\fP is \fINULL\fP a random -pathname is selected. This pathname adheres to the convention set forth -by -.BR bus (5). -.PP -If \fIfile\fP is not \fINULL\fP the -.BR bus_create () -function fails if the file already exists if \fIflags\fP contains -\fIBUS_EXCL\fP. Otherwise if \fIfile\fP is not \fINULL\fP, the -.BR bus_create () -function does nothing if the file already exists. -.PP -If \fIflags\fP contains \fIBUS_INTR\fP, the function fails if it is -interrupted. -.PP -Unless \fIout_file\fP is \fINULL\fP, the pathname of the bus should be -stored in a new char array stored in \fI*out_file\fP. The caller must -free the allocated stored in \fI*out_file\fP. -.SH RETURN VALUES -Upon successful completion, the function returns 0. Otherwise the -function return -1 with \fIerrno\fP set to indicate the error. -.SH ERRORS -.TP -.B ENOMEM -The process cannot allocate more memory. -.PP -The -.BR bus_create (3) -function may also fail and set \fIerrno\fP to any -of the errors specified for the routines -.BR open (2) -and -.BR write (2). -.SH SEE ALSO -.BR bus-create (1), -.BR bus (5), -.BR libbus (7), -.BR bus_unlink (3), -.BR bus_open (3), -.BR open (2), -.BR write (2) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus_open.3 b/doc/man/bus_open.3 deleted file mode 100644 index 683ae9b..0000000 --- a/doc/man/bus_open.3 +++ /dev/null @@ -1,67 +0,0 @@ -.TH BUS_OPEN 3 BUS-%VERSION% -.SH NAME -bus_open - Open a bus -.SH SYNOPSIS -.LP -.nf -#include -.P -int bus_open(bus_t *\fIbus\fP, const char *\fIfile\fP, int \fIflags\fP); -.fi -.SH DESCRIPTION -The -.BR bus_open () -function acquires resources required for the process to use the bus -associated with the filename stored in \fIfile\fP. The function also -stores the resources in \fIbus\fP for use by other -.BR bus -functions. -.PP -Values for \fIflags\fP are constructed by a bitwise inclusive OR of -flags from the following list. -.TP -.B BUS_RDONLY -The process will only be using the bus for receiving messages. -.TP -.B BUS_WRONLY -The process will only be using the bus for sending messages. -.TP -.B BUS_RDWR -The process will use the bus for both receiving and sending messages. -.SH RETURN VALUES -Upon successful completion the function returns 0. Otherwise the -function returns -1 and set \fIerrno\fP to indicate the error. -.SH ERRORS -.TP -.B ENOMEM -The process cannot allocate more memory. -.TP -.B EACCES -Operation permission is denied to the calling process. -.TP -.B EINVAL -The described bus does not exist. -.PP -The -.BR bus_open () -function may also fail and set \fIerrno\fP to any of the errors -specified for the routine -.BR open (2). -.SH SEE ALSO -.BR bus-create (1), -.BR bus (5), -.BR libbus (7), -.BR bus_close (3), -.BR bus_unlink (3), -.BR bus_write (3), -.BR bus_read (3), -.BR bus_poll (3), -.BR open (2) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus_poll.3 b/doc/man/bus_poll.3 deleted file mode 100644 index 95271ca..0000000 --- a/doc/man/bus_poll.3 +++ /dev/null @@ -1,87 +0,0 @@ -.TH BUS_POLL 3 BUS-%VERSION% -.SH NAME -bus_poll_start, bus_poll_stop, bus_poll, bus_poll_timed - Wait a message to be broadcasted -.SH SYNOPSIS -.LP -.nf -#include -.P -int bus_poll_start(bus_t *\fIbus\fP); -int bus_poll_stop(const bus_t *\fIbus\fP); -const char *bus_poll(bus_t *\fIbus\fP, int \fIflags\fP); -const char *bus_poll_timed(bus_t *\fIbus\fP, const struct timespec *\fItimeout\fP, clockid_t \fIclockid\fP); -.fi -.SH DESCRIPTION -The -.BR bus_poll () -function waits for a message to be broadcasted on the \fIbus\fP, and return -the message it receives. The function fails if (\fIflags\fP &BUS_NOWAIT) -and there is not already a message waiting on the bus. Received messages -shall be copied and parsed, and acted upon, in a separate thread, and -.BR bus_poll () -or -.BR bus_poll_stop () -called again as soon as possible. -.PP -The -.BR bus_poll_start () -funcion must be called before -.BR bus_poll () -is called for the first time. When the process is done listening on the -bus it must call the -.BR bus_poll_stop () -function. -.PP -The -.BR bus_poll_timed () -function behaves like -.BR bus_poll (), -except if it is not able to read a message within the specified time, -it will fail and set \fIerrno\fP to \fBEAGAIN\fP. The time is specified -as an absolute time using the parameter \fItimeout\fP. The behaviour is -unspecified if \fItimeout\fP is \fINULL\fP. \fItimeout\fP is measured -with the clock whose ID is specified by the \fIclockid\fP parameter. This -clock must be a predicitable clock. -.SH RETURN VALUES -Upon successful completion, the functions -.BR bus_poll_start () -and -.BR bus_poll_stop () -returns 0. Otherwise the functions returns -1 and sets \fIerrno\fP to -indicate the error. -.PP -Upon successful completion, the functions -.BR bus_poll () -and -.BR bus_poll_timed () -returns the received message. Otherwise the function returns \fINULL\fP -and sets \fIerrno\fP to indicate the error. -.SH ERRORS -The -.BR bus_poll (3), -.BR bus_poll_start (3) -and -.BR bus_poll_stop (3) -functions may fail and set \fIerrno\fP to any of the errors specified for -.BR semop (3). -The -.BR bus_poll_timed (3) -function may also set \fIerrno\fP to any of the errors specified for -.BR clock_gettime (3). -.SH SEE ALSO -.BR bus-create (1), -.BR bus (5), -.BR libbus (7), -.BR bus_open (3), -.BR bus_write (3), -.BR bus_read (3), -.BR semop (3), -.BR clock_gettime (3) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus_read.3 b/doc/man/bus_read.3 deleted file mode 100644 index 1caadfb..0000000 --- a/doc/man/bus_read.3 +++ /dev/null @@ -1,79 +0,0 @@ -.TH BUS_READ 3 BUS-%VERSION% -.SH NAME -bus_read, bus_read_timed - Listen for new messages a bus -.SH SYNOPSIS -.LP -.nf -#include -.P -int bus_read(const bus_t *\fIbus\fP, int (*\fIcallback\fP)(const char *\fImessage\fP, void *\fIuser_data\fP), - void *\fIuser_data\fP); -int bus_read_timed(const bus_t *\fIbus\fP, int (*\fIcallback\fP)(const char *\fImessage\fP, void *\fIuser_data\fP), - void *\fIuser_data\fP, const struct timespec *\fItimeout\fP, clockid_t \fIclockid\fP); -.fi -.SH DESCRIPTION -The -.BR bus_read () -function waits for new message to be sent on the bus specified in -\fIbus\fP, as provieded by a previous call to the -.BR bus_open () -function. Once a message is received, the \fIcallback\fP function is -invoked. The \fImessage\fP argument to the callback is the received -message, and \fIuser_data\fP for \fIcallback\fP should be -\fIuser_data\fP from -.BR bus_read (). -However, once -.BR bus_read () -has ensured that it will receive any message sent on the bus, it shall -invoke the \fIcallback\fP function with \fImessage\fP set to \fINULL\fP, -to notify the process that it can perform any action that requires that -it is listening on the bus. -.PP -After \fIcallback\fP returns, \fImessage\fP may be override. Therefore -\fIcallback\fP should copy \fImessage\fP and start a new thread that -uses the copy of \fImessage\fP. \fIcallback\fP shall return -1 on -failure, 0 if -.BR bus_read () -should stop listening or 1 if -.BR bus_read () -should continue listening. -.PP -The -.BR bus_read_timed () -function behaves like -.BR bus_read (), -except it will automatically fail and set \fIerrno\fP to \fBEAGAIN\fP -when the specified time has passed. The time is specified as an -absolute time using the parameter \fItimeout\fP. The behaviour is -unspecified if \fItimeout\fP is \fINULL\fP. \fItimeout\fP is measured -with the clock whose ID is specified by the \fIclockid\fP parameter. -This clock must be a predicitable clock. -.SH RETURN VALUES -Upon successful completion, these functions returns 0. Otherwise the -function returns -1 and sets \fIerrno\fP to indicate the error. -.SH ERRORS -The -.BR bus_read (3) -function may fail and set \fIerrno\fP to any of the errors specified for -.BR semop (3). -The -.BR bus_read_timed (3) -function may also set \fIerrno\fP to any of the errors specified for -.BR clock_gettime (3). -.SH SEE ALSO -.BR bus-create (1), -.BR bus (5), -.BR libbus (7), -.BR bus_open (3), -.BR bus_write (3), -.BR bus_poll (3), -.BR semop (3), -.BR clock_gettime (3) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus_unlink.3 b/doc/man/bus_unlink.3 deleted file mode 100644 index 83ec760..0000000 --- a/doc/man/bus_unlink.3 +++ /dev/null @@ -1,56 +0,0 @@ -.TH BUS_UNLINK 3 BUS-%VERSION% -.SH NAME -bus_unlink - Remove a bus -.SH SYNOPSIS -.LP -.nf -#include -.P -int bus_unlink(const char *\fIfile\fP); -.fi -.SH DESCRIPTION -The -.BR bus_unlink () -function removes the bus assoicated with the pathname stored in -\fIfile\fP. The function also unlinks the file. -.SH RETURN VALUES -Upon successful completion, the function returns 0. Otherwise the -function returns -1 and sets \fIerrno\fP to indicate the error. -.SH ERRORS -.TP -.B EINVAL -The bus does not exist. -.TP -.B EACCES -Operation permission is denied to the calling process. -.TP -.B EPERM -The user does not have permission to remove the bus. -.PP -The -.BR bus_unlink (3) -function may also fail and set \fIerrno\fP to any of the errors -specified for the routines -.BR unlink (2), -.BR open (2), -.BR semget (3) -and -.BR shmget (3). -.SH SEE ALSO -.BR bus-create (1), -.BR bus (5), -.BR libbus (7), -.BR bus_create (3), -.BR bus_close (3), -.BR unlink (2), -.BR open (2), -.BR semget (3), -.BR shmget (3) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/bus_write.3 b/doc/man/bus_write.3 deleted file mode 100644 index 4513c29..0000000 --- a/doc/man/bus_write.3 +++ /dev/null @@ -1,64 +0,0 @@ -.TH BUS_WRITE 3 BUS-%VERSION% -.SH NAME -bus_write, bus_write_timed - Broadcast a message a bus -.SH SYNOPSIS -.LP -.nf -#include -.P -int bus_write(const bus_t *\fIbus\fP, const char *\fImessage\fP, int \fIflags\fP); -int bus_write_timed(const bus_t *\fIbus\fP, const char *\fImessage\fP, - const struct timespec *\fItimeout\fP, clockid_t \fIclockid\fP); -.fi -.SH DESCRIPTION -The -.BR bus_write () -function broadcasts a message on the bus whose information is stored in -\fIbus\fP. The message read by the function is stored in the parameter -\fImessage\fP. It may not exceeed 2048 bytes, including NULL termination. -.PP -The -.BR bus_write () -function shall fail, and set \fIerrno\fP to \fIEAGAIN\fP, if the call -would suspend the process and (\fIflags\fP &BUS_NOWAIT). -.PP -The -.BR bus_write_timed () -function behaves like -.BR bus_write (), -except if it is not able to write the \fImessage\fP within the specified -time, it will fail and set \fIerrno\fP to \fBEAGAIN\fP. The time is -specified as an absolute time using the parameter \fItimeout\fP. The -behaviour is unspecified if \fItimeout\fP is \fINULL\fP. \fItimeout\fP -is measured with the clock whose ID is specified by the \fIclockid\fP -parameter. This clock must be a predicitable clock. -.SH RETURN VALUES -Upon successful completion, these functions returns 0. Otherwise the -function returns -1 and sets \fIerrno\fP to indicate the error. -.SH ERRORS -The -.BR bus_write (3) -function may fail and set \fIerrno\fP to any of the errors specified for -.BR semop (3). -The -.BR bus_write_timed (3) -function may also set \fIerrno\fP to any of the errors specified for -.BR clock_gettime (3). -.SH SEE ALSO -.BR bus-create (1), -.BR bus (5), -.BR libbus (7), -.BR bus_open (3), -.BR bus_read (3), -.BR bus_poll (3), -.BR bus_chown (3), -.BR bus_chmod (3), -.BR clock_gettime (3) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/man/libbus.7 b/doc/man/libbus.7 deleted file mode 100644 index 804ee97..0000000 --- a/doc/man/libbus.7 +++ /dev/null @@ -1,39 +0,0 @@ -.TH LIBBUS 7 BUS-%VERSION% -.SH NAME -libbus - A simple daemonless system for broadcasting messages locally -.SH DESCRIPTION -.BR bus -is a stupid-simple, thrilless, daemonless interprocess communication -system for broadcasting messages. -.SH RATIONALE -We need an interprocess communication system similar to message queues. -But we need broadcasting rather than anycasting, so we have a fast, -simple and daemonless system for announcing events to any processes that -might be interested. -.SH FUTURE DIRECTION -None. -.SH SEE ALSO -.BR bus (1), -.BR bus (5), -.BR bus_create (3), -.BR bus_unlink (3), -.BR bus_open (3), -.BR bus_close (3), -.BR bus_write (3), -.BR bus_write_timed (3), -.BR bus_read (3), -.BR bus_read_timed (3), -.BR bus_poll_start (3), -.BR bus_poll_stop (3), -.BR bus_poll (3), -.BR bus_poll_timed (3), -.BR bus_chown (3), -.BR bus_chmod (3) -.SH AUTHORS -Principal author, Mattias Andrée. See the LICENSE file for the full -list of authors. -.SH LICENSE -MIT/X Consortium License. -.SH BUGS -Please report bugs to https://github.com/maandree/bus/issues or to -maandree@member.fsf.org diff --git a/doc/protocol b/doc/protocol index f45799e..116c1b2 100644 --- a/doc/protocol +++ b/doc/protocol @@ -45,4 +45,3 @@ and `V(a)` is done when exiting the scope. It also means that these actions [P(a) and V(a)] are undone when the process exits, or if the call fails. `with V(a)` is to `V(a)` as `with P(a)` is to `P(a)`. - diff --git a/fdl.texinfo b/fdl.texinfo new file mode 100644 index 0000000..cb71f05 --- /dev/null +++ b/fdl.texinfo @@ -0,0 +1,505 @@ +@c The GNU Free Documentation License. +@center Version 1.3, 3 November 2008 + +@c This file is intended to be included within another document, +@c hence no sectioning command or @node. + +@display +Copyright @copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. +@uref{http://fsf.org/} + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. +@end display + +@enumerate 0 +@item +PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document @dfn{free} in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of ``copyleft'', which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + +@item +APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The ``Document'', below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as ``you''. You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A ``Modified Version'' of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A ``Secondary Section'' is a named appendix or a front-matter section +of the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall +subject (or to related matters) and contains nothing that could fall +directly within that overall subject. (Thus, if the Document is in +part a textbook of mathematics, a Secondary Section may not explain +any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The ``Invariant Sections'' are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The ``Cover Texts'' are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A ``Transparent'' copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not ``Transparent'' is called ``Opaque''. + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, La@TeX{} input +format, SGML or XML using a publicly available +DTD, and standard-conforming simple HTML, +PostScript or PDF designed for human modification. Examples +of transparent image formats include PNG, XCF and +JPG. Opaque formats include proprietary formats that can be +read and edited only by proprietary word processors, SGML or +XML for which the DTD and/or processing tools are +not generally available, and the machine-generated HTML, +PostScript or PDF produced by some word processors for +output purposes only. + +The ``Title Page'' means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, ``Title Page'' means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +The ``publisher'' means any person or entity that distributes copies +of the Document to the public. + +A section ``Entitled XYZ'' means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as ``Acknowledgements'', +``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title'' +of such a section when you modify the Document means that it remains a +section ``Entitled XYZ'' according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +@item +VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + +@item +COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + +@item +MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +@enumerate A +@item +Use in the Title Page (and on the covers, if any) a title distinct +from that of the Document, and from those of previous versions +(which should, if there were any, be listed in the History section +of the Document). You may use the same title as a previous version +if the original publisher of that version gives permission. + +@item +List on the Title Page, as authors, one or more persons or entities +responsible for authorship of the modifications in the Modified +Version, together with at least five of the principal authors of the +Document (all of its principal authors, if it has fewer than five), +unless they release you from this requirement. + +@item +State on the Title page the name of the publisher of the +Modified Version, as the publisher. + +@item +Preserve all the copyright notices of the Document. + +@item +Add an appropriate copyright notice for your modifications +adjacent to the other copyright notices. + +@item +Include, immediately after the copyright notices, a license notice +giving the public permission to use the Modified Version under the +terms of this License, in the form shown in the Addendum below. + +@item +Preserve in that license notice the full lists of Invariant Sections +and required Cover Texts given in the Document's license notice. + +@item +Include an unaltered copy of this License. + +@item +Preserve the section Entitled ``History'', Preserve its Title, and add +to it an item stating at least the title, year, new authors, and +publisher of the Modified Version as given on the Title Page. If +there is no section Entitled ``History'' in the Document, create one +stating the title, year, authors, and publisher of the Document as +given on its Title Page, then add an item describing the Modified +Version as stated in the previous sentence. + +@item +Preserve the network location, if any, given in the Document for +public access to a Transparent copy of the Document, and likewise +the network locations given in the Document for previous versions +it was based on. These may be placed in the ``History'' section. +You may omit a network location for a work that was published at +least four years before the Document itself, or if the original +publisher of the version it refers to gives permission. + +@item +For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve +the Title of the section, and preserve in the section all the +substance and tone of each of the contributor acknowledgements and/or +dedications given therein. + +@item +Preserve all the Invariant Sections of the Document, +unaltered in their text and in their titles. Section numbers +or the equivalent are not considered part of the section titles. + +@item +Delete any section Entitled ``Endorsements''. Such a section +may not be included in the Modified Version. + +@item +Do not retitle any existing section to be Entitled ``Endorsements'' or +to conflict in title with any Invariant Section. + +@item +Preserve any Warranty Disclaimers. +@end enumerate + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled ``Endorsements'', provided it contains +nothing but endorsements of your Modified Version by various +parties---for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + +@item +COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled ``History'' +in the various original documents, forming one section Entitled +``History''; likewise combine any sections Entitled ``Acknowledgements'', +and any sections Entitled ``Dedications''. You must delete all +sections Entitled ``Endorsements.'' + +@item +COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + +@item +AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an ``aggregate'' if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + +@item +TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled ``Acknowledgements'', +``Dedications'', or ``History'', the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + +@item +TERMINATION + +You may not copy, modify, sublicense, or distribute the Document +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense, or distribute it is void, and +will automatically terminate your rights under this License. + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, receipt of a copy of some or all of the same material does +not give you any rights to use it. + +@item +FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +@uref{http://www.gnu.org/copyleft/}. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License ``or any later version'' applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. If the Document +specifies that a proxy can decide which future versions of this +License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the +Document. + +@item +RELICENSING + +``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any +World Wide Web server that publishes copyrightable works and also +provides prominent facilities for anybody to edit those works. A +public wiki that anybody can edit is an example of such a server. A +``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the +site means any set of copyrightable works thus published on the MMC +site. + +``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0 +license published by Creative Commons Corporation, a not-for-profit +corporation with a principal place of business in San Francisco, +California, as well as future copyleft versions of that license +published by that same organization. + +``Incorporate'' means to publish or republish a Document, in whole or +in part, as part of another Document. + +An MMC is ``eligible for relicensing'' if it is licensed under this +License, and if all works that were first published under this License +somewhere other than this MMC, and subsequently incorporated in whole +or in part into the MMC, (1) had no cover texts or invariant sections, +and (2) were thus incorporated prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site +under CC-BY-SA on the same site at any time before August 1, 2009, +provided the MMC is eligible for relicensing. + +@end enumerate + +@page +@heading ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + +@smallexample +@group + Copyright (C) @var{year} @var{your name}. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + Texts. A copy of the license is included in the section entitled ``GNU + Free Documentation License''. +@end group +@end smallexample + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the ``with@dots{}Texts.''@: line with this: + +@smallexample +@group + with the Invariant Sections being @var{list their titles}, with + the Front-Cover Texts being @var{list}, and with the Back-Cover Texts + being @var{list}. +@end group +@end smallexample + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. + +@c Local Variables: +@c ispell-local-pdict: "ispell-dict" +@c End: diff --git a/libbus.7 b/libbus.7 new file mode 100644 index 0000000..3565b50 --- /dev/null +++ b/libbus.7 @@ -0,0 +1,31 @@ +.TH LIBBUS 7 BUS +.SH NAME +libbus - A simple daemonless system for broadcasting messages locally +.SH DESCRIPTION +.BR bus +is a stupid-simple, thrilless, daemonless interprocess communication +system for broadcasting messages. +.SH RATIONALE +We need an interprocess communication system similar to message queues. +But we need broadcasting rather than anycasting, so we have a fast, +simple and daemonless system for announcing events to any processes that +might be interested. +.SH FUTURE DIRECTION +None. +.SH SEE ALSO +.BR bus (1), +.BR bus (5), +.BR bus_create (3), +.BR bus_unlink (3), +.BR bus_open (3), +.BR bus_close (3), +.BR bus_write (3), +.BR bus_write_timed (3), +.BR bus_read (3), +.BR bus_read_timed (3), +.BR bus_poll_start (3), +.BR bus_poll_stop (3), +.BR bus_poll (3), +.BR bus_poll_timed (3), +.BR bus_chown (3), +.BR bus_chmod (3) diff --git a/libbus.c b/libbus.c new file mode 100644 index 0000000..0a84353 --- /dev/null +++ b/libbus.c @@ -0,0 +1,1173 @@ +/* See LICENSE file for copyright and license details. */ +#include "bus.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_EVEN_HARDER +# ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER +# define BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER +# endif +#endif +#ifdef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER +# ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS +# define BUS_SEMAPHORES_ARE_SYNCHRONOUS +# endif +#endif + + +/** + * Semaphore used to signal `bus_write` that `bus_read` is ready + */ +#define S 0 + +/** + * Semaphore for making `bus_write` wait while `bus_read` is reseting `S` + */ +#define W 1 + +/** + * Binary semaphore for making `bus_write` exclusively locked + */ +#define X 2 + +/** + * Semaphore used to cue `bus_read` that it may read the shared memory + */ +#define Q 3 + +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_EVEN_HARDER +/** + * Semaphore used to notify `bus_read` that it may restore `S` + */ +# define N 4 + +/** + * The number of semaphores in the semaphore array + */ +# define BUS_SEMAPHORES 5 +#else +# define BUS_SEMAPHORES 4 +#endif + +/** + * The default permission mits of the bus + */ +#define DEFAULT_MODE 0600 + + + +/** + * Decrease the value of a semaphore by 1 + * + * @param bus:const bus_t * The bus + * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` + * @param flags:int `SEM_UNDO` if the action should be undone when the program exits, + * `IPC_NOWAIT` if the action should fail if it would block + * @return :int 0 on success, -1 on error + */ +#define acquire_semaphore(bus, semaphore, flags) \ + semaphore_op(bus, semaphore, -1, flags) + +/** + * Increase the value of a semaphore by 1 + * + * @param bus:const bus_t * The bus + * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` + * @param flags:int `SEM_UNDO` if the action should be undone when the program exits + * @return :int 0 on success, -1 on error + */ +#define release_semaphore(bus, semaphore, flags) \ + semaphore_op(bus, semaphore, +1, flags) + +/** + * Wait for the value of a semaphore to become 0 + * + * @param bus:const bus_t * The bus + * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` + * @param flags:int `IPC_NOWAIT` if the action should fail if it would block + * @return :int 0 on success, -1 on error + */ +#define zero_semaphore(bus, semaphore, flags) \ + semaphore_op(bus, semaphore, 0, flags) + +/** + * Decrease the value of a semaphore by 1 + * + * @param bus:const bus_t * The bus + * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` + * @param flags:int `SEM_UNDO` if the action should be undone when the program exits, + * `IPC_NOWAIT` if the action should fail if it would block + * @param timeout:const struct timespec * The amount of time to wait before failing + * @return :int 0 on success, -1 on error + */ +#define acquire_semaphore_timed(bus, semaphore, flags, timeout) \ + semaphore_op_timed(bus, semaphore, -1, flags, timeout) + +/** + * Increase the value of a semaphore by 1 + * + * @param bus:const bus_t * The bus + * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` + * @param flags:int `SEM_UNDO` if the action should be undone when the program exits + * @param timeout:const struct timespec * The amount of time to wait before failing + * @return :int 0 on success, -1 on error + */ +#define release_semaphore_timed(bus, semaphore, flags, timeout) \ + semaphore_op_timed(bus, semaphore, +1, flags, timeout) + +/** + * Wait for the value of a semaphore to become 0 + * + * @param bus:const bus_t * The bus + * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` + * @param flags:int `IPC_NOWAIT` if the action should fail if it would block + * @param timeout:const struct timespec * The amount of time to wait before failing + * @return :int 0 on success, -1 on error + */ +#define zero_semaphore_timed(bus, semaphore, flags, timeout) \ + semaphore_op_timed(bus, semaphore, 0, flags, timeout) + +/** + * Open the semaphore array + * + * @param bus:const bus_t * The bus + * @return :int 0 on success, -1 on error + */ +#define open_semaphores(bus) \ + (((bus)->sem_id = semget((bus)->key_sem, BUS_SEMAPHORES, 0)) == -1 ? -1 : 0) + +/** + * Write a message to the shared memory + * + * @param bus:const bus_t * The bus + * @param msg:const char * The message + * @return :int 0 on success, -1 on error + */ +#define write_shared_memory(bus, msg) \ + (memcpy((bus)->message, msg, (strlen(msg) + 1) * sizeof(char))) + + +/** + * Set `delta` to the convertion of `timeout` from absolute to relative time, + * measured in the clock whose ID is specified by `clockid` + * + * @scope timeout:struct timespec Output variable for relative time + * @scope timeout:const struct timespec * The absolute time + * @scope clockid:clockid_t The clock time is measured + */ +#define DELTA \ + do { \ + if (absolute_time_to_delta_time(&delta, timeout, clockid) < 0) { \ + goto fail; \ + } else if ((delta.tv_sec < 0) || (delta.tv_nsec < 0)) { \ + errno = EAGAIN; \ + goto fail; \ + }\ + } while (0) + + +/** + * If `flags & (bus_flag)`, this macro evalutes to `sys_flag`, + * otherwise this macro evalutes to 0. + */ +#define F(bus_flag, sys_flag) \ + ((flags & (bus_flag)) ? sys_flag : 0) + + + +/** + * Statement wrapper that goes to `fail` on failure + */ +#define t(inst) \ + do { if ((inst) == -1) goto fail; } while (0) + + + +#ifdef _SEM_SEMUN_UNDEFINED +union semun { + int val; + struct semid_ds *buf; + unsigned short *array; +}; +#endif + + + +/** + * Create a semaphore array for the bus + * + * @param bus Bus information to fill with the key of the created semaphore array + * @return 0 on success, -1 on error + */ +static int +create_semaphores(bus_t *bus) +{ + int id = -1, rint, saved_errno; + double r; + union semun values; + + values.array = NULL; + + /* Create semaphore array. */ + for (;;) { + rint = rand(); + r = (double)rint; + r /= (double)RAND_MAX + 1; + r *= (1 << (8 * sizeof(key_t) - 2)) - 1; + bus->key_sem = (key_t)r + 1; + if (bus->key_sem == IPC_PRIVATE) + continue; + id = semget(bus->key_sem, BUS_SEMAPHORES, IPC_CREAT | IPC_EXCL | DEFAULT_MODE); + if (id != -1) + break; + if ((errno != EEXIST) && (errno != EINTR)) + goto fail; + } + + /* Initialise the array. */ + values.array = calloc((size_t)BUS_SEMAPHORES, sizeof(unsigned short)); + if (!values.array) + goto fail; + values.array[X] = 1; + if (semctl(id, 0, SETALL, values.array) == -1) + goto fail; + free(values.array); + values.array = NULL; + + return 0; + +fail: + saved_errno = errno; + if (id != -1) + semctl(id, 0, IPC_RMID); + free(values.array); + errno = saved_errno; + return -1; +} + + +/** + * Create a shared memory for the bus + * + * @param bus Bus information to fill with the key of the created shared memory + * @return 0 on success, -1 on error + */ +static int +create_shared_memory(bus_t *bus) +{ + int id = -1, rint, saved_errno; + double r; + struct shmid_ds _info; + + /* Create shared memory. */ + for (;;) { + rint = rand(); + r = (double)rint; + r /= (double)RAND_MAX + 1; + r *= (1 << (8 * sizeof(key_t) - 2)) - 1; + bus->key_shm = (key_t)r + 1; + if (bus->key_shm == IPC_PRIVATE) + continue; + id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, IPC_CREAT | IPC_EXCL | DEFAULT_MODE); + if (id != -1) + break; + if ((errno != EEXIST) && (errno != EINTR)) + goto fail; + } + + return 0; + +fail: + saved_errno = errno; + if (id != -1) + shmctl(id, IPC_RMID, &_info); + errno = saved_errno; + return -1; +} + + +/** + * Remove the semaphore array for the bus + * + * @param bus Bus information + * @return 0 on success, -1 on error + */ +static int +remove_semaphores(const bus_t *bus) +{ + int id = semget(bus->key_sem, BUS_SEMAPHORES, 0); + return ((id == -1) || (semctl(id, 0, IPC_RMID) == -1)) ? -1 : 0; +} + + +/** + * Remove the shared memory for the bus + * + * @param bus Bus information + * @return 0 on success, -1 on error + */ +static int +remove_shared_memory(const bus_t *bus) +{ + struct shmid_ds _info; + int id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, 0); + return ((id == -1) || (shmctl(id, IPC_RMID, &_info) == -1)) ? -1 : 0; +} + + +/** + * Increase or decrease the value of a semaphore, or wait the it to become 0 + * + * @param bus Bus information + * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` + * @param delta The adjustment to make to the semaphore's value, 0 to wait for it to become 0 + * @param flags `SEM_UNDO` if the action should be undone when the program exits + * @return 0 on success, -1 on error + */ +static int +semaphore_op(const bus_t *bus, int semaphore, int delta, int flags) +{ + struct sembuf op; + op.sem_num = (unsigned short)semaphore; + op.sem_op = (short)delta; + op.sem_flg = (short)flags; + return semop(bus->sem_id, &op, (size_t)1); +} + + +/** + * Increase or decrease the value of a semaphore, or wait the it to become 0 + * + * @param bus Bus information + * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` + * @param delta The adjustment to make to the semaphore's value, 0 to wait for it to become 0 + * @param flags `SEM_UNDO` if the action should be undone when the program exits + * @param timeout The amount of time to wait before failing + * @return 0 on success, -1 on error + */ +static int +semaphore_op_timed(const bus_t *bus, int semaphore, int delta, int flags, const struct timespec *timeout) +{ + struct sembuf op; + op.sem_num = (unsigned short)semaphore; + op.sem_op = (short)delta; + op.sem_flg = (short)flags; + return semtimedop(bus->sem_id, &op, (size_t)1, timeout); +} + + +/** + * Set the value of a semaphore + * + * @param bus Bus information + * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` + * @param value The new value of the semaphore + * @return 0 on success, -1 on error + */ +static int +write_semaphore(const bus_t *bus, unsigned semaphore, int value) +{ + union semun semval; + semval.val = value; + return semctl(bus->sem_id, (unsigned short)semaphore, SETVAL, semval); +} + + +/** + * Open the shared memory for the bus + * + * @param bus Bus information + * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR` + * @return 0 on success, -1 on error + */ +static int +open_shared_memory(bus_t *bus, int flags) +{ + int id; + void *address; + t(id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, 0)); + address = shmat(id, NULL, (flags & BUS_RDONLY) ? SHM_RDONLY : 0); + if ((address == (void *)-1) || !address) + goto fail; + bus->message = (char *)address; + return 0; +fail: + return -1; +} + + +/** + * Close the shared memory for the bus + * + * @param bus Bus information + * @return 0 on success, -1 on error + */ +static int +close_shared_memory(bus_t *bus) +{ + t(shmdt(bus->message)); + bus->message = NULL; + return 0; +fail: + return -1; +} + + +/** + * Get a random ASCII letter or digit + * + * @return A random ASCII letter or digit + */ +static char +randomchar(void) +{ + int rint = rand(); + double r = (double)rint; + r /= (double)RAND_MAX + 1; + r *= 10 + 26 + 26; + return "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"[(int)r]; +} + + +/** + * Basically, this is `mkdir -p -m $mode $pathname` + * + * @param pathname The pathname of the directory to create if missing + * @param mode The permission bits of any created directory + * @return 0 on sucess, -1 on error + */ +static int +mkdirs(char *pathname, mode_t mode) +{ + size_t i, n = strlen(pathname); + char c; + for (i = 0; i < n; i++) + if (pathname[i] != '/') + break; + for (; i < n; i++) { + if (pathname[i] == '/') { + c = pathname[i]; + if (access(pathname, F_OK)) + if (mkdir(pathname, mode) < 0) + return -1; + pathname[i] = c; + break; + } + } + if (access(pathname, F_OK)) + if (mkdir(pathname, mode) < 0) + return -1; + return 0; +} + + +/** + * Convert an absolute time to a relative time + * + * @param delta Output parameter for the relative time + * @param absolute The absolute time + * @param clockid The ID of the clock the time is measured in + * @return 0 on success, -1 on error + */ +static int +absolute_time_to_delta_time(struct timespec *delta, const struct timespec *absolute, clockid_t clockid) +{ + if (clock_gettime(clockid, delta) < 0) + return -1; + + delta->tv_sec = absolute->tv_sec - delta->tv_sec; + delta->tv_nsec = absolute->tv_nsec - delta->tv_nsec; + + if (delta->tv_nsec < 0L) { + delta->tv_nsec += 1000000000L; + delta->tv_sec -= 1; + } + if (delta->tv_nsec >= 1000000000L) { + delta->tv_nsec -= 1000000000L; + delta->tv_sec += 1; + } + + return 0; +} + + + +/** + * Create a new bus + * + * @param file The pathname of the bus, `NULL` to create a random one + * @param flags `BUS_EXCL` (if `file` is not `NULL`) to fail if the file + * already exists, otherwise if the file exists, nothing + * will happen; + * `BUS_INTR` to fail if interrupted + * @param out_file Output parameter for the pathname of the bus + * @return 0 on success, -1 on error + */ +int +bus_create(const char *restrict file, int flags, char **restrict out_file) +{ + int fd = -1, saved_errno; + bus_t bus; + char buf[1 + 2 * (3 * sizeof(ssize_t) + 2)]; + size_t ptr, len; + ssize_t wrote; + char *genfile = NULL; + const char *env; + + if (out_file) + *out_file = NULL; + + bus.sem_id = -1; + bus.key_sem = -1; + bus.key_shm = -1; + bus.message = NULL; + bus.first_poll = 0; + + srand((unsigned int)time(NULL) + (unsigned int)rand()); + + if (file) { + fd = open(file, O_WRONLY | O_CREAT | O_EXCL, DEFAULT_MODE); + if (fd == -1) { + if ((errno != EEXIST) || (flags & BUS_EXCL)) + return -1; + goto done; + } + } else { + env = getenv("XDG_RUNTIME_DIR"); + if (!env || !*env) + env = "/run"; + genfile = malloc((strlen(env) + 6 + 7 + 30) * sizeof(char)); + if (!genfile) + goto fail; + if (out_file) + *out_file = genfile; + sprintf(genfile, "%s/bus", env); + t(mkdirs(genfile, 0755)); + sprintf(genfile, "%s/bus/random.", env); + len = strlen(genfile); + genfile[len + 30] = '\0'; + retry: + for (ptr = 0; ptr < 30; ptr++) + genfile[len + ptr] = randomchar(); + fd = open(genfile, O_WRONLY | O_CREAT | O_EXCL, DEFAULT_MODE); + if (fd == -1) { + if (errno == EEXIST) + goto retry; + return -1; + } + } + + t(create_semaphores(&bus)); + t(create_shared_memory(&bus)); + + sprintf(buf, "%zi\n%zi\n", (ssize_t)(bus.key_sem), (ssize_t)(bus.key_shm)); + for (len = strlen(buf), ptr = 0; ptr < len;) { + wrote = write(fd, buf + ptr, len - ptr); + if (wrote < 0) { + if ((errno != EINTR) || (flags & BUS_INTR)) + goto fail; + } else { + ptr += (size_t)wrote; + } + } + close(fd); + +done: + if (out_file && !*out_file) { + len = strlen(file) + 1; + *out_file = malloc(len * sizeof(char)); + memcpy(*out_file, file, len * sizeof(char)); + } else if (!out_file) { + free(genfile); + } + return 0; + +fail: + saved_errno = errno; + if (bus.key_sem) + remove_semaphores(&bus); + if (bus.key_shm) + remove_shared_memory(&bus); + if (fd == -1) + close(fd); + if (out_file) + *out_file = NULL; + free(genfile); + unlink(file); + errno = saved_errno; + return -1; +} + + +/** + * Remove a bus + * + * @param file The pathname of the bus + * @return 0 on success, -1 on error + */ +int +bus_unlink(const char *file) +{ + int r = 0, saved_errno = 0; + bus_t bus; + t(bus_open(&bus, file, -1)); + + r |= remove_semaphores(&bus); + if (r && !saved_errno) + saved_errno = errno; + + r |= remove_shared_memory(&bus); + if (r && !saved_errno) + saved_errno = errno; + + r |= unlink(file); + if (r && !saved_errno) + saved_errno = errno; + + errno = saved_errno; + return r; +fail: + return -1; +} + + +/** + * Open an existing bus + * + * @param bus Bus information to fill + * @param file The filename of the bus + * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR` + * any negative value is used internally + * for telling the function to not actually + * opening the bus, but just to parse the file + * @return 0 on success, -1 on error + */ +int +bus_open(bus_t *restrict bus, const char *restrict file, int flags) +{ + int saved_errno; + char *line = NULL; + size_t len = 0; + FILE *f; + + bus->sem_id = -1; + bus->key_sem = -1; + bus->key_shm = -1; + bus->message = NULL; + + f = fopen(file, "r"); + + t(getline(&line, &len, f)); + t(bus->key_sem = (key_t)atoll(line)); + free(line), line = NULL, len = 0; + + t(getline(&line, &len, f)); + t(bus->key_shm = (key_t)atoll(line)); + free(line), line = NULL; + + fclose(f); + + if (flags >= 0) { + t(open_semaphores(bus)); + t(open_shared_memory(bus, flags)); + } + return 0; +fail: + saved_errno = errno; + free(line); + errno = saved_errno; + return -1; +} + + +/** + * Close a bus + * + * @param bus Bus information + * @return 0 on success, -1 on error + */ +int +bus_close(bus_t *bus) +{ + bus->sem_id = -1; + if (bus->message) + t(close_shared_memory(bus)); + bus->message = NULL; + return 0; + +fail: + return -1; +} + + +/** + * Broadcast a message on a bus + * + * @param bus Bus information + * @param message The message to write, may not be longer than + * `BUS_MEMORY_SIZE` including the NUL-termination + * @param flags `BUS_NOWAIT` if this function shall fail if + * another process is currently running this + * procedure + * @return 0 on success, -1 on error + */ +int +bus_write(const bus_t *bus, const char *message, int flags) +{ + int saved_errno; +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS + int state = 0; +#endif + if (acquire_semaphore(bus, X, SEM_UNDO | F(BUS_NOWAIT, IPC_NOWAIT)) == -1) + return -1; + t(zero_semaphore(bus, W, 0)); + write_shared_memory(bus, message); +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS + t(release_semaphore(bus, N, SEM_UNDO)); state++; +#endif + t(write_semaphore(bus, Q, 0)); + t(zero_semaphore(bus, S, 0)); +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS + t(acquire_semaphore(bus, N, SEM_UNDO)); state--; +#endif + t(release_semaphore(bus, X, SEM_UNDO)); + return 0; + +fail: + saved_errno = errno; +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS + if (state > 0) + acquire_semaphore(bus, N, SEM_UNDO); +#endif + release_semaphore(bus, X, SEM_UNDO); + errno = saved_errno; + return -1; +} + + +/** + * Broadcast a message on a bus + * + * @param bus Bus information + * @param message The message to write, may not be longer than + * `BUS_MEMORY_SIZE` including the NUL-termination + * @param timeout The time the operation shall fail with errno set + * to `EAGAIN` if not completed + * @param clockid The ID of the clock the `timeout` is measured with, + * it most be a predictable clock + * @return 0 on success, -1 on error + */ +int bus_write_timed(const bus_t *bus, const char *message, + const struct timespec *timeout, clockid_t clockid) +{ + int saved_errno; +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS + int state = 0; +#endif + struct timespec delta; + if (!timeout) + return bus_write(bus, message, 0); + + DELTA; + if (acquire_semaphore_timed(bus, X, SEM_UNDO, &delta) == -1) + return -1; + DELTA; + t(zero_semaphore_timed(bus, W, 0, &delta)); + write_shared_memory(bus, message); +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS + t(release_semaphore(bus, N, SEM_UNDO)); state++; +#endif + t(write_semaphore(bus, Q, 0)); + t(zero_semaphore(bus, S, 0)); +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS + t(acquire_semaphore(bus, N, SEM_UNDO)); state--; +#endif + t(release_semaphore(bus, X, SEM_UNDO)); + return 0; + +fail: + saved_errno = errno; +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS + if (state > 0) + acquire_semaphore(bus, N, SEM_UNDO); +#endif + release_semaphore(bus, X, SEM_UNDO); + errno = saved_errno; + return -1; +} + + +/** + * Listen (in a loop, forever) for new message on a bus + * + * @param bus Bus information + * @param callback Function to call when a message is received, the + * input parameters will be the read message and + * `user_data` from `bus_read`'s parameter with the + * same name. The message must have been parsed or + * copied when `callback` returns as it may be over + * overridden after that time. `callback` should + * return either of the the values: + * * 0: stop listening + * * 1: continue listening + * * -1: an error has occurred + * However, the function [`bus_read`] will invoke + * `callback` with `message` set to `NULL`one time + * directly after it has started listening on the + * bus. This is to the the program now it can safely + * continue with any action that requires that the + * programs is listening on the bus. + * @param user_data Parameter passed to `callback` + * @return 0 on success, -1 on error + */ +int +bus_read(const bus_t *restrict bus, int (*callback)(const char *message, void *user_data), void *user_data) +{ + int r, state = 0, saved_errno; + if (release_semaphore(bus, S, SEM_UNDO) == -1) + return -1; + t(r = callback(NULL, user_data)); + if (!r) goto done; + for (;;) { + t(release_semaphore(bus, Q, 0)); + t(zero_semaphore(bus, Q, 0)); + t(r = callback(bus->message, user_data)); + if (!r) goto done; + t(release_semaphore(bus, W, SEM_UNDO)); state++; + t(acquire_semaphore(bus, S, SEM_UNDO)); state++; + t(zero_semaphore(bus, S, 0)); +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER + t(zero_semaphore(bus, N, 0)); +#endif + t(release_semaphore(bus, S, SEM_UNDO)); state--; + t(acquire_semaphore(bus, W, SEM_UNDO)); state--; + } + +fail: + saved_errno = errno; + if (state > 1) + release_semaphore(bus, S, SEM_UNDO); + if (state > 0) + acquire_semaphore(bus, W, SEM_UNDO); + acquire_semaphore(bus, S, SEM_UNDO); + errno = saved_errno; + return -1; + +done: + t(acquire_semaphore(bus, S, SEM_UNDO)); + return 0; +} + + +/** + * Listen (in a loop, forever) for new message on a bus + * + * @param bus Bus information + * @param callback Function to call when a message is received, the + * input parameters will be the read message and + * `user_data` from `bus_read`'s parameter with the + * same name. The message must have been parsed or + * copied when `callback` returns as it may be over + * overridden after that time. `callback` should + * return either of the the values: + * * 0: stop listening + * * 1: continue listening + * * -1: an error has occurred + * However, the function [`bus_read`] will invoke + * `callback` with `message` set to `NULL`one time + * directly after it has started listening on the + * bus. This is to the the program now it can safely + * continue with any action that requires that the + * programs is listening on the bus. + * @param user_data Parameter passed to `callback` + * @param timeout The time the operation shall fail with errno set + * to `EAGAIN` if not completed, note that the callback + * function may or may not have been called + * @param clockid The ID of the clock the `timeout` is measured with, + * it most be a predictable clock + * @return 0 on success, -1 on error + */ +int bus_read_timed(const bus_t *restrict bus, int (*callback)(const char *message, void *user_data), + void *user_data, const struct timespec *timeout, clockid_t clockid) +{ + int r, state = 0, saved_errno; + struct timespec delta; + if (!timeout) + return bus_read(bus, callback, user_data); + + DELTA; + if (release_semaphore_timed(bus, S, SEM_UNDO, &delta) == -1) + return -1; + t(r = callback(NULL, user_data)); + if (!r) goto done; + for (;;) { + DELTA; + t(release_semaphore_timed(bus, Q, 0, &delta)); + DELTA; + t(zero_semaphore_timed(bus, Q, 0, &delta)); + t(r = callback(bus->message, user_data)); + if (!r) goto done; + t(release_semaphore(bus, W, SEM_UNDO)); state++; + t(acquire_semaphore(bus, S, SEM_UNDO)); state++; + t(zero_semaphore(bus, S, 0)); +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER + t(zero_semaphore(bus, N, 0)); +#endif + t(release_semaphore(bus, S, SEM_UNDO)); state--; + t(acquire_semaphore(bus, W, SEM_UNDO)); state--; + } + +fail: + saved_errno = errno; + if (state > 1) + release_semaphore(bus, S, SEM_UNDO); + if (state > 0) + acquire_semaphore(bus, W, SEM_UNDO); + acquire_semaphore(bus, S, SEM_UNDO); + errno = saved_errno; + return -1; + +done: + t(acquire_semaphore(bus, S, SEM_UNDO)); + return 0; +} + + +/** + * Announce that the thread is listening on the bus. + * This is required so the will does not miss any + * messages due to race conditions. Additionally, + * not calling this function will cause the bus the + * misbehave, is `bus_poll` is written to expect + * this function to have been called. + * + * @param bus Bus information + * @return 0 on success, -1 on error + */ +int +bus_poll_start(bus_t *bus) +{ + bus->first_poll = 1; + t(release_semaphore(bus, S, SEM_UNDO)); + t(release_semaphore(bus, Q, 0)); + return 0; + +fail: + return -1; +} + + +/** + * Announce that the thread has stopped listening on the bus. + * This is required so that the thread does not cause others + * to wait indefinitely. + * + * @param bus Bus information + * @return 0 on success, -1 on error + */ +int +bus_poll_stop(const bus_t *bus) +{ + return acquire_semaphore(bus, S, SEM_UNDO | IPC_NOWAIT); +} + + +/** + * Wait for a message to be broadcasted on the bus. + * The caller should make a copy of the received message, + * without freeing the original copy, and parse it in a + * separate thread. When the new thread has started be + * started, the caller of this function should then + * either call `bus_poll` again or `bus_poll_stop`. + * + * @param bus Bus information + * @param flags `BUS_NOWAIT` if the bus should fail and set `errno` to + * `EAGAIN` if there isn't already a message available on the bus + * @return The received message, `NULL` on error + */ +const char * +bus_poll(bus_t *bus, int flags) +{ + int state = 0, saved_errno; + if (!bus->first_poll) { + t(release_semaphore(bus, W, SEM_UNDO)); state++; + t(acquire_semaphore(bus, S, SEM_UNDO)); state++; + t(zero_semaphore(bus, S, 0)); +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER + t(zero_semaphore(bus, N, 0)); +#endif + t(release_semaphore(bus, S, SEM_UNDO)); state--; + t(acquire_semaphore(bus, W, SEM_UNDO)); state--; + t(release_semaphore(bus, Q, 0)); + } else { + bus->first_poll = 0; + } + state--; + t(zero_semaphore(bus, Q, F(BUS_NOWAIT, IPC_NOWAIT))); + return bus->message; + +fail: + saved_errno = errno; + if (state > 1) + release_semaphore(bus, S, SEM_UNDO); + if (state > 0) + acquire_semaphore(bus, W, SEM_UNDO); + if (state < 0) + bus->first_poll = 1; + errno = saved_errno; + return NULL; +} + + +/** + * Wait for a message to be broadcasted on the bus. + * The caller should make a copy of the received message, + * without freeing the original copy, and parse it in a + * separate thread. When the new thread has started be + * started, the caller of this function should then + * either call `bus_poll_timed` again or `bus_poll_stop`. + * + * @param bus Bus information + * @param timeout The time the operation shall fail with errno set + * to `EAGAIN` if not completed + * @param clockid The ID of the clock the `timeout` is measured with, + * it most be a predictable clock + * @return The received message, `NULL` on error + */ +const char *bus_poll_timed(bus_t *bus, const struct timespec *timeout, clockid_t clockid) +{ + int state = 0, saved_errno; + struct timespec delta; + if (!timeout) + return bus_poll(bus, 0); + + if (!bus->first_poll) { + t(release_semaphore(bus, W, SEM_UNDO)); state++; + t(acquire_semaphore(bus, S, SEM_UNDO)); state++; + t(zero_semaphore(bus, S, 0)); +#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER + t(zero_semaphore(bus, N, 0)); +#endif + t(release_semaphore(bus, S, SEM_UNDO)); state--; + t(acquire_semaphore(bus, W, SEM_UNDO)); state--; + t(release_semaphore(bus, Q, 0)); + } else { + bus->first_poll = 0; + } + state--; + DELTA; + t(zero_semaphore_timed(bus, Q, 0, &delta)); + return bus->message; + +fail: + saved_errno = errno; + if (state > 1) + release_semaphore(bus, S, SEM_UNDO); + if (state > 0) + acquire_semaphore(bus, W, SEM_UNDO); + if (state < 0) + bus->first_poll = 1; + errno = saved_errno; + return NULL; +} + + +/** + * Change the ownership of a bus + * + * `stat(2)` can be used of the bus's associated file to get the bus's ownership + * + * @param file The pathname of the bus + * @param owner The user ID of the bus's new owner + * @param group The group ID of the bus's new group + * @return 0 on success, -1 on error + */ +int +bus_chown(const char *file, uid_t owner, gid_t group) +{ + bus_t bus; + struct semid_ds sem_stat; + struct shmid_ds shm_stat; + int shm_id; + + t(bus_open(&bus, file, -1)); + t(chown(file, owner, group)); + + /* chown sem */ + t(open_semaphores(&bus)); + t(semctl(bus.sem_id, 0, IPC_STAT, &sem_stat)); + sem_stat.sem_perm.uid = owner; + sem_stat.sem_perm.gid = group; + t(semctl(bus.sem_id, 0, IPC_SET, &sem_stat)); + + /* chown shm */ + t(shm_id = shmget(bus.key_shm, (size_t)BUS_MEMORY_SIZE, 0)); + t(shmctl(shm_id, IPC_STAT, &shm_stat)); + shm_stat.shm_perm.uid = owner; + shm_stat.shm_perm.gid = group; + t(shmctl(shm_id, IPC_SET, &shm_stat)); + + return 0; +fail: + return -1; +} + + +/** + * Change the permissions for a bus + * + * `stat(2)` can be used of the bus's associated file to get the bus's permissions + * + * @param file The pathname of the bus + * @param mode The permissions of the bus, any permission for a user implies + * full permissions for that user, except only the owner may + * edit the bus's associated file + * @return 0 on success, -1 on error + */ +int +bus_chmod(const char *file, mode_t mode) +{ + bus_t bus; + mode_t fmode; + struct semid_ds sem_stat; + struct shmid_ds shm_stat; + int shm_id; + + mode = (mode & S_IRWXU) ? (mode | S_IRWXU) : (mode & (mode_t)~S_IRWXU); + mode = (mode & S_IRWXG) ? (mode | S_IRWXG) : (mode & (mode_t)~S_IRWXG); + mode = (mode & S_IRWXO) ? (mode | S_IRWXO) : (mode & (mode_t)~S_IRWXO); + mode &= (S_IWUSR | S_IWGRP | S_IWOTH | S_IRUSR | S_IRGRP | S_IROTH); + fmode = mode & (mode_t)~(S_IWGRP | S_IWOTH); + + t(bus_open(&bus, file, -1)); + t(chmod(file, fmode)); + + /* chmod sem */ + t(open_semaphores(&bus)); + t(semctl(bus.sem_id, 0, IPC_STAT, &sem_stat)); + sem_stat.sem_perm.mode = (unsigned short)mode; + t(semctl(bus.sem_id, 0, IPC_SET, &sem_stat)); + + /* chmod shm */ + t(shm_id = shmget(bus.key_shm, (size_t)BUS_MEMORY_SIZE, 0)); + t(shmctl(shm_id, IPC_STAT, &shm_stat)); + shm_stat.shm_perm.mode = (unsigned short)mode; + t(shmctl(shm_id, IPC_SET, &shm_stat)); + + return 0; +fail: + return -1; +} diff --git a/src/bus.c b/src/bus.c deleted file mode 100644 index e9dd789..0000000 --- a/src/bus.c +++ /dev/null @@ -1,1197 +0,0 @@ -/** - * MIT/X Consortium License - * - * Copyright © 2015 Mattias Andrée - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#define _XOPEN_SOURCE 700 -#define _GNU_SOURCE -#include "bus.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - - -#ifdef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_EVEN_HARDER -# ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER -# define BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER -# endif -#endif -#ifdef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER -# ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS -# define BUS_SEMAPHORES_ARE_SYNCHRONOUS -# endif -#endif - - - -/** - * Semaphore used to signal `bus_write` that `bus_read` is ready - */ -#define S 0 - -/** - * Semaphore for making `bus_write` wait while `bus_read` is reseting `S` - */ -#define W 1 - -/** - * Binary semaphore for making `bus_write` exclusively locked - */ -#define X 2 - -/** - * Semaphore used to cue `bus_read` that it may read the shared memory - */ -#define Q 3 - -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_EVEN_HARDER -/** - * Semaphore used to notify `bus_read` that it may restore `S` - */ -#define N 4 - -/** - * The number of semaphores in the semaphore array - */ -#define BUS_SEMAPHORES 5 -#else -#define BUS_SEMAPHORES 4 -#endif - -/** - * The default permission mits of the bus - */ -#define DEFAULT_MODE 0600 - - - -/** - * Decrease the value of a semaphore by 1 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `SEM_UNDO` if the action should be undone when the program exits, - * `IPC_NOWAIT` if the action should fail if it would block - * @return :int 0 on success, -1 on error - */ -#define acquire_semaphore(bus, semaphore, flags) \ - semaphore_op(bus, semaphore, -1, flags) - -/** - * Increase the value of a semaphore by 1 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `SEM_UNDO` if the action should be undone when the program exits - * @return :int 0 on success, -1 on error - */ -#define release_semaphore(bus, semaphore, flags) \ - semaphore_op(bus, semaphore, +1, flags) - -/** - * Wait for the value of a semaphore to become 0 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `IPC_NOWAIT` if the action should fail if it would block - * @return :int 0 on success, -1 on error - */ -#define zero_semaphore(bus, semaphore, flags) \ - semaphore_op(bus, semaphore, 0, flags) - -/** - * Decrease the value of a semaphore by 1 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `SEM_UNDO` if the action should be undone when the program exits, - * `IPC_NOWAIT` if the action should fail if it would block - * @param timeout:const struct timespec * The amount of time to wait before failing - * @return :int 0 on success, -1 on error - */ -#define acquire_semaphore_timed(bus, semaphore, flags, timeout) \ - semaphore_op_timed(bus, semaphore, -1, flags, timeout) - -/** - * Increase the value of a semaphore by 1 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `SEM_UNDO` if the action should be undone when the program exits - * @param timeout:const struct timespec * The amount of time to wait before failing - * @return :int 0 on success, -1 on error - */ -#define release_semaphore_timed(bus, semaphore, flags, timeout) \ - semaphore_op_timed(bus, semaphore, +1, flags, timeout) - -/** - * Wait for the value of a semaphore to become 0 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `IPC_NOWAIT` if the action should fail if it would block - * @param timeout:const struct timespec * The amount of time to wait before failing - * @return :int 0 on success, -1 on error - */ -#define zero_semaphore_timed(bus, semaphore, flags, timeout) \ - semaphore_op_timed(bus, semaphore, 0, flags, timeout) - -/** - * Open the semaphore array - * - * @param bus:const bus_t * The bus - * @return :int 0 on success, -1 on error - */ -#define open_semaphores(bus) \ - (((bus)->sem_id = semget((bus)->key_sem, BUS_SEMAPHORES, 0)) == -1 ? -1 : 0) - -/** - * Write a message to the shared memory - * - * @param bus:const bus_t * The bus - * @param msg:const char * The message - * @return :int 0 on success, -1 on error - */ -#define write_shared_memory(bus, msg) \ - (memcpy((bus)->message, msg, (strlen(msg) + 1) * sizeof(char))) - - -/** - * Set `delta` to the convertion of `timeout` from absolute to relative time, - * measured in the clock whose ID is specified by `clockid` - * - * @scope timeout:struct timespec Output variable for relative time - * @scope timeout:const struct timespec * The absolute time - * @scope clockid:clockid_t The clock time is measured - */ -#define DELTA \ - do { \ - if (absolute_time_to_delta_time(&delta, timeout, clockid) < 0) goto fail; \ - else if ((delta.tv_sec < 0) || (delta.tv_nsec < 0)) { errno = EAGAIN; goto fail; } \ - } while (0) - - -/** - * If `flags & (bus_flag)`, this macro evalutes to `sys_flag`, - * otherwise this macro evalutes to 0. - */ -#define F(bus_flag, sys_flag) \ - ((flags & (bus_flag)) ? sys_flag : 0) - - - -/** - * Statement wrapper that goes to `fail` on failure - */ -#define t(inst) \ - if ((inst) == -1) goto fail - - - -#ifdef _SEM_SEMUN_UNDEFINED -union semun { - int val; - struct semid_ds *buf; - unsigned short *array; -}; -#endif - - - -/** - * Create a semaphore array for the bus - * - * @param bus Bus information to fill with the key of the created semaphore array - * @return 0 on success, -1 on error - */ -static int -create_semaphores(bus_t *bus) -{ - int id = -1, rint, saved_errno; - double r; - union semun values; - - values.array = NULL; - - /* Create semaphore array. */ - for (;;) { - rint = rand(); - r = (double)rint; - r /= (double)RAND_MAX + 1; - r *= (1 << (8 * sizeof(key_t) - 2)) - 1; - bus->key_sem = (key_t)r + 1; - if (bus->key_sem == IPC_PRIVATE) - continue; - id = semget(bus->key_sem, BUS_SEMAPHORES, IPC_CREAT | IPC_EXCL | DEFAULT_MODE); - if (id != -1) - break; - if ((errno != EEXIST) && (errno != EINTR)) - goto fail; - } - - /* Initialise the array. */ - values.array = calloc((size_t)BUS_SEMAPHORES, sizeof(unsigned short)); - if (!values.array) - goto fail; - values.array[X] = 1; - if (semctl(id, 0, SETALL, values.array) == -1) - goto fail; - free(values.array); - values.array = NULL; - - return 0; - -fail: - saved_errno = errno; - if (id != -1) - semctl(id, 0, IPC_RMID); - free(values.array); - errno = saved_errno; - return -1; -} - - -/** - * Create a shared memory for the bus - * - * @param bus Bus information to fill with the key of the created shared memory - * @return 0 on success, -1 on error - */ -static int -create_shared_memory(bus_t *bus) -{ - int id = -1, rint, saved_errno; - double r; - struct shmid_ds _info; - - /* Create shared memory. */ - for (;;) { - rint = rand(); - r = (double)rint; - r /= (double)RAND_MAX + 1; - r *= (1 << (8 * sizeof(key_t) - 2)) - 1; - bus->key_shm = (key_t)r + 1; - if (bus->key_shm == IPC_PRIVATE) - continue; - id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, IPC_CREAT | IPC_EXCL | DEFAULT_MODE); - if (id != -1) - break; - if ((errno != EEXIST) && (errno != EINTR)) - goto fail; - } - - return 0; - -fail: - saved_errno = errno; - if (id != -1) - shmctl(id, IPC_RMID, &_info); - errno = saved_errno; - return -1; -} - - -/** - * Remove the semaphore array for the bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -static int -remove_semaphores(const bus_t *bus) -{ - int id = semget(bus->key_sem, BUS_SEMAPHORES, 0); - return ((id == -1) || (semctl(id, 0, IPC_RMID) == -1)) ? -1 : 0; -} - - -/** - * Remove the shared memory for the bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -static int -remove_shared_memory(const bus_t *bus) -{ - struct shmid_ds _info; - int id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, 0); - return ((id == -1) || (shmctl(id, IPC_RMID, &_info) == -1)) ? -1 : 0; -} - - -/** - * Increase or decrease the value of a semaphore, or wait the it to become 0 - * - * @param bus Bus information - * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` - * @param delta The adjustment to make to the semaphore's value, 0 to wait for it to become 0 - * @param flags `SEM_UNDO` if the action should be undone when the program exits - * @return 0 on success, -1 on error - */ -static int -semaphore_op(const bus_t *bus, int semaphore, int delta, int flags) -{ - struct sembuf op; - op.sem_num = (unsigned short)semaphore; - op.sem_op = (short)delta; - op.sem_flg = (short)flags; - return semop(bus->sem_id, &op, (size_t)1); -} - - -/** - * Increase or decrease the value of a semaphore, or wait the it to become 0 - * - * @param bus Bus information - * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` - * @param delta The adjustment to make to the semaphore's value, 0 to wait for it to become 0 - * @param flags `SEM_UNDO` if the action should be undone when the program exits - * @param timeout The amount of time to wait before failing - * @return 0 on success, -1 on error - */ -static int -semaphore_op_timed(const bus_t *bus, int semaphore, int delta, int flags, const struct timespec *timeout) -{ - struct sembuf op; - op.sem_num = (unsigned short)semaphore; - op.sem_op = (short)delta; - op.sem_flg = (short)flags; - return semtimedop(bus->sem_id, &op, (size_t)1, timeout); -} - - -/** - * Set the value of a semaphore - * - * @param bus Bus information - * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` - * @param value The new value of the semaphore - * @return 0 on success, -1 on error - */ -static int -write_semaphore(const bus_t *bus, unsigned semaphore, int value) -{ - union semun semval; - semval.val = value; - return semctl(bus->sem_id, (unsigned short)semaphore, SETVAL, semval); -} - - -/** - * Open the shared memory for the bus - * - * @param bus Bus information - * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR` - * @return 0 on success, -1 on error - */ -static int -open_shared_memory(bus_t *bus, int flags) -{ - int id; - void *address; - t(id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, 0)); - address = shmat(id, NULL, (flags & BUS_RDONLY) ? SHM_RDONLY : 0); - if ((address == (void *)-1) || !address) - goto fail; - bus->message = (char *)address; - return 0; -fail: - return -1; -} - - -/** - * Close the shared memory for the bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -static int -close_shared_memory(bus_t *bus) -{ - t(shmdt(bus->message)); - bus->message = NULL; - return 0; -fail: - return -1; -} - - -/** - * Get a random ASCII letter or digit - * - * @return A random ASCII letter or digit - */ -static char -randomchar(void) -{ - int rint = rand(); - double r = (double)rint; - r /= (double)RAND_MAX + 1; - r *= 10 + 26 + 26; - return "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"[(int)r]; -} - - -/** - * Basically, this is `mkdir -p -m $mode $pathname` - * - * @param pathname The pathname of the directory to create if missing - * @param mode The permission bits of any created directory - * @return 0 on sucess, -1 on error - */ -static int -mkdirs(char *pathname, mode_t mode) -{ - size_t i, n = strlen(pathname); - char c; - for (i = 0; i < n; i++) - if (pathname[i] != '/') - break; - for (; i < n; i++) { - if (pathname[i] == '/') { - c = pathname[i]; - if (access(pathname, F_OK)) - if (mkdir(pathname, mode) < 0) - return -1; - pathname[i] = c; - break; - } - } - if (access(pathname, F_OK)) - if (mkdir(pathname, mode) < 0) - return -1; - return 0; -} - - -/** - * Convert an absolute time to a relative time - * - * @param delta Output parameter for the relative time - * @param absolute The absolute time - * @param clockid The ID of the clock the time is measured in - * @return 0 on success, -1 on error - */ -static int -absolute_time_to_delta_time(struct timespec *delta, const struct timespec *absolute, clockid_t clockid) -{ - if (clock_gettime(clockid, delta) < 0) - return -1; - - delta->tv_sec = absolute->tv_sec - delta->tv_sec; - delta->tv_nsec = absolute->tv_nsec - delta->tv_nsec; - - if (delta->tv_nsec < 0L) { - delta->tv_nsec += 1000000000L; - delta->tv_sec -= 1; - } - if (delta->tv_nsec >= 1000000000L) { - delta->tv_nsec -= 1000000000L; - delta->tv_sec += 1; - } - - return 0; -} - - - -/** - * Create a new bus - * - * @param file The pathname of the bus, `NULL` to create a random one - * @param flags `BUS_EXCL` (if `file` is not `NULL`) to fail if the file - * already exists, otherwise if the file exists, nothing - * will happen; - * `BUS_INTR` to fail if interrupted - * @param out_file Output parameter for the pathname of the bus - * @return 0 on success, -1 on error - */ -int -bus_create(const char *restrict file, int flags, char **restrict out_file) -{ - int fd = -1, saved_errno; - bus_t bus; - char buf[1 + 2 * (3 * sizeof(ssize_t) + 2)]; - size_t ptr, len; - ssize_t wrote; - char *genfile = NULL; - const char *env; - - if (out_file) - *out_file = NULL; - - bus.sem_id = -1; - bus.key_sem = -1; - bus.key_shm = -1; - bus.message = NULL; - bus.first_poll = 0; - - srand((unsigned int)time(NULL) + (unsigned int)rand()); - - if (file) { - fd = open(file, O_WRONLY | O_CREAT | O_EXCL, DEFAULT_MODE); - if (fd == -1) { - if ((errno != EEXIST) || (flags & BUS_EXCL)) - return -1; - goto done; - } - } else { - env = getenv("XDG_RUNTIME_DIR"); - if (!env || !*env) - env = "/run"; - genfile = malloc((strlen(env) + 6 + 7 + 30) * sizeof(char)); - if (!genfile) - goto fail; - if (out_file) - *out_file = genfile; - sprintf(genfile, "%s/bus", env); - t(mkdirs(genfile, 0755)); - sprintf(genfile, "%s/bus/random.", env); - len = strlen(genfile); - genfile[len + 30] = '\0'; - retry: - for (ptr = 0; ptr < 30; ptr++) - genfile[len + ptr] = randomchar(); - fd = open(genfile, O_WRONLY | O_CREAT | O_EXCL, DEFAULT_MODE); - if (fd == -1) { - if (errno == EEXIST) - goto retry; - return -1; - } - } - - t(create_semaphores(&bus)); - t(create_shared_memory(&bus)); - - sprintf(buf, "%zi\n%zi\n", (ssize_t)(bus.key_sem), (ssize_t)(bus.key_shm)); - for (len = strlen(buf), ptr = 0; ptr < len;) { - wrote = write(fd, buf + ptr, len - ptr); - if (wrote < 0) { - if ((errno != EINTR) || (flags & BUS_INTR)) - goto fail; - } else { - ptr += (size_t)wrote; - } - } - close(fd); - -done: - if (out_file && !*out_file) { - len = strlen(file) + 1; - *out_file = malloc(len * sizeof(char)); - memcpy(*out_file, file, len * sizeof(char)); - } else if (!out_file) { - free(genfile); - } - return 0; - -fail: - saved_errno = errno; - if (bus.key_sem) - remove_semaphores(&bus); - if (bus.key_shm) - remove_shared_memory(&bus); - if (fd == -1) - close(fd); - if (out_file) - *out_file = NULL; - free(genfile); - unlink(file); - errno = saved_errno; - return -1; -} - - -/** - * Remove a bus - * - * @param file The pathname of the bus - * @return 0 on success, -1 on error - */ -int -bus_unlink(const char *file) -{ - int r = 0, saved_errno = 0; - bus_t bus; - t(bus_open(&bus, file, -1)); - - r |= remove_semaphores(&bus); - if (r && !saved_errno) - saved_errno = errno; - - r |= remove_shared_memory(&bus); - if (r && !saved_errno) - saved_errno = errno; - - r |= unlink(file); - if (r && !saved_errno) - saved_errno = errno; - - errno = saved_errno; - return r; -fail: - return -1; -} - - -/** - * Open an existing bus - * - * @param bus Bus information to fill - * @param file The filename of the bus - * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR` - * any negative value is used internally - * for telling the function to not actually - * opening the bus, but just to parse the file - * @return 0 on success, -1 on error - */ -int -bus_open(bus_t *restrict bus, const char *restrict file, int flags) -{ - int saved_errno; - char *line = NULL; - size_t len = 0; - FILE *f; - - bus->sem_id = -1; - bus->key_sem = -1; - bus->key_shm = -1; - bus->message = NULL; - - f = fopen(file, "r"); - - t(getline(&line, &len, f)); - t(bus->key_sem = (key_t)atoll(line)); - free(line), line = NULL, len = 0; - - t(getline(&line, &len, f)); - t(bus->key_shm = (key_t)atoll(line)); - free(line), line = NULL; - - fclose(f); - - if (flags >= 0) { - t(open_semaphores(bus)); - t(open_shared_memory(bus, flags)); - } - return 0; -fail: - saved_errno = errno; - free(line); - errno = saved_errno; - return -1; -} - - -/** - * Close a bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int -bus_close(bus_t *bus) -{ - bus->sem_id = -1; - if (bus->message) - t(close_shared_memory(bus)); - bus->message = NULL; - return 0; - -fail: - return -1; -} - - -/** - * Broadcast a message on a bus - * - * @param bus Bus information - * @param message The message to write, may not be longer than - * `BUS_MEMORY_SIZE` including the NUL-termination - * @param flags `BUS_NOWAIT` if this function shall fail if - * another process is currently running this - * procedure - * @return 0 on success, -1 on error - */ -int -bus_write(const bus_t *bus, const char *message, int flags) -{ - int saved_errno; -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - int state = 0; -#endif - if (acquire_semaphore(bus, X, SEM_UNDO | F(BUS_NOWAIT, IPC_NOWAIT)) == -1) - return -1; - t(zero_semaphore(bus, W, 0)); - write_shared_memory(bus, message); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - t(release_semaphore(bus, N, SEM_UNDO)); state++; -#endif - t(write_semaphore(bus, Q, 0)); - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - t(acquire_semaphore(bus, N, SEM_UNDO)); state--; -#endif - t(release_semaphore(bus, X, SEM_UNDO)); - return 0; - -fail: - saved_errno = errno; -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - if (state > 0) - acquire_semaphore(bus, N, SEM_UNDO); -#endif - release_semaphore(bus, X, SEM_UNDO); - errno = saved_errno; - return -1; -} - - -/** - * Broadcast a message on a bus - * - * @param bus Bus information - * @param message The message to write, may not be longer than - * `BUS_MEMORY_SIZE` including the NUL-termination - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return 0 on success, -1 on error - */ -int bus_write_timed(const bus_t *bus, const char *message, - const struct timespec *timeout, clockid_t clockid) -{ - int saved_errno; -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - int state = 0; -#endif - struct timespec delta; - if (!timeout) - return bus_write(bus, message, 0); - - DELTA; - if (acquire_semaphore_timed(bus, X, SEM_UNDO, &delta) == -1) - return -1; - DELTA; - t(zero_semaphore_timed(bus, W, 0, &delta)); - write_shared_memory(bus, message); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - t(release_semaphore(bus, N, SEM_UNDO)); state++; -#endif - t(write_semaphore(bus, Q, 0)); - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - t(acquire_semaphore(bus, N, SEM_UNDO)); state--; -#endif - t(release_semaphore(bus, X, SEM_UNDO)); - return 0; - -fail: - saved_errno = errno; -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - if (state > 0) - acquire_semaphore(bus, N, SEM_UNDO); -#endif - release_semaphore(bus, X, SEM_UNDO); - errno = saved_errno; - return -1; -} - - -/** - * Listen (in a loop, forever) for new message on a bus - * - * @param bus Bus information - * @param callback Function to call when a message is received, the - * input parameters will be the read message and - * `user_data` from `bus_read`'s parameter with the - * same name. The message must have been parsed or - * copied when `callback` returns as it may be over - * overridden after that time. `callback` should - * return either of the the values: - * * 0: stop listening - * * 1: continue listening - * * -1: an error has occurred - * However, the function [`bus_read`] will invoke - * `callback` with `message` set to `NULL`one time - * directly after it has started listening on the - * bus. This is to the the program now it can safely - * continue with any action that requires that the - * programs is listening on the bus. - * @param user_data Parameter passed to `callback` - * @return 0 on success, -1 on error - */ -int -bus_read(const bus_t *restrict bus, int (*callback)(const char *message, void *user_data), void *user_data) -{ - int r, state = 0, saved_errno; - if (release_semaphore(bus, S, SEM_UNDO) == -1) - return -1; - t(r = callback(NULL, user_data)); - if (!r) goto done; - for (;;) { - t(release_semaphore(bus, Q, 0)); - t(zero_semaphore(bus, Q, 0)); - t(r = callback(bus->message, user_data)); - if (!r) goto done; - t(release_semaphore(bus, W, SEM_UNDO)); state++; - t(acquire_semaphore(bus, S, SEM_UNDO)); state++; - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER - t(zero_semaphore(bus, N, 0)); -#endif - t(release_semaphore(bus, S, SEM_UNDO)); state--; - t(acquire_semaphore(bus, W, SEM_UNDO)); state--; - } - -fail: - saved_errno = errno; - if (state > 1) - release_semaphore(bus, S, SEM_UNDO); - if (state > 0) - acquire_semaphore(bus, W, SEM_UNDO); - acquire_semaphore(bus, S, SEM_UNDO); - errno = saved_errno; - return -1; - -done: - t(acquire_semaphore(bus, S, SEM_UNDO)); - return 0; -} - - -/** - * Listen (in a loop, forever) for new message on a bus - * - * @param bus Bus information - * @param callback Function to call when a message is received, the - * input parameters will be the read message and - * `user_data` from `bus_read`'s parameter with the - * same name. The message must have been parsed or - * copied when `callback` returns as it may be over - * overridden after that time. `callback` should - * return either of the the values: - * * 0: stop listening - * * 1: continue listening - * * -1: an error has occurred - * However, the function [`bus_read`] will invoke - * `callback` with `message` set to `NULL`one time - * directly after it has started listening on the - * bus. This is to the the program now it can safely - * continue with any action that requires that the - * programs is listening on the bus. - * @param user_data Parameter passed to `callback` - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed, note that the callback - * function may or may not have been called - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return 0 on success, -1 on error - */ -int bus_read_timed(const bus_t *restrict bus, int (*callback)(const char *message, void *user_data), - void *user_data, const struct timespec *timeout, clockid_t clockid) -{ - int r, state = 0, saved_errno; - struct timespec delta; - if (!timeout) - return bus_read(bus, callback, user_data); - - DELTA; - if (release_semaphore_timed(bus, S, SEM_UNDO, &delta) == -1) - return -1; - t(r = callback(NULL, user_data)); - if (!r) goto done; - for (;;) { - DELTA; - t(release_semaphore_timed(bus, Q, 0, &delta)); - DELTA; - t(zero_semaphore_timed(bus, Q, 0, &delta)); - t(r = callback(bus->message, user_data)); - if (!r) goto done; - t(release_semaphore(bus, W, SEM_UNDO)); state++; - t(acquire_semaphore(bus, S, SEM_UNDO)); state++; - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER - t(zero_semaphore(bus, N, 0)); -#endif - t(release_semaphore(bus, S, SEM_UNDO)); state--; - t(acquire_semaphore(bus, W, SEM_UNDO)); state--; - } - -fail: - saved_errno = errno; - if (state > 1) - release_semaphore(bus, S, SEM_UNDO); - if (state > 0) - acquire_semaphore(bus, W, SEM_UNDO); - acquire_semaphore(bus, S, SEM_UNDO); - errno = saved_errno; - return -1; - -done: - t(acquire_semaphore(bus, S, SEM_UNDO)); - return 0; -} - - -/** - * Announce that the thread is listening on the bus. - * This is required so the will does not miss any - * messages due to race conditions. Additionally, - * not calling this function will cause the bus the - * misbehave, is `bus_poll` is written to expect - * this function to have been called. - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int -bus_poll_start(bus_t *bus) -{ - bus->first_poll = 1; - t(release_semaphore(bus, S, SEM_UNDO)); - t(release_semaphore(bus, Q, 0)); - return 0; - -fail: - return -1; -} - - -/** - * Announce that the thread has stopped listening on the bus. - * This is required so that the thread does not cause others - * to wait indefinitely. - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int -bus_poll_stop(const bus_t *bus) -{ - return acquire_semaphore(bus, S, SEM_UNDO | IPC_NOWAIT); -} - - -/** - * Wait for a message to be broadcasted on the bus. - * The caller should make a copy of the received message, - * without freeing the original copy, and parse it in a - * separate thread. When the new thread has started be - * started, the caller of this function should then - * either call `bus_poll` again or `bus_poll_stop`. - * - * @param bus Bus information - * @param flags `BUS_NOWAIT` if the bus should fail and set `errno` to - * `EAGAIN` if there isn't already a message available on the bus - * @return The received message, `NULL` on error - */ -const char * -bus_poll(bus_t *bus, int flags) -{ - int state = 0, saved_errno; - if (!bus->first_poll) { - t(release_semaphore(bus, W, SEM_UNDO)); state++; - t(acquire_semaphore(bus, S, SEM_UNDO)); state++; - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER - t(zero_semaphore(bus, N, 0)); -#endif - t(release_semaphore(bus, S, SEM_UNDO)); state--; - t(acquire_semaphore(bus, W, SEM_UNDO)); state--; - t(release_semaphore(bus, Q, 0)); - } else { - bus->first_poll = 0; - } - state--; - t(zero_semaphore(bus, Q, F(BUS_NOWAIT, IPC_NOWAIT))); - return bus->message; - -fail: - saved_errno = errno; - if (state > 1) - release_semaphore(bus, S, SEM_UNDO); - if (state > 0) - acquire_semaphore(bus, W, SEM_UNDO); - if (state < 0) - bus->first_poll = 1; - errno = saved_errno; - return NULL; -} - - -/** - * Wait for a message to be broadcasted on the bus. - * The caller should make a copy of the received message, - * without freeing the original copy, and parse it in a - * separate thread. When the new thread has started be - * started, the caller of this function should then - * either call `bus_poll_timed` again or `bus_poll_stop`. - * - * @param bus Bus information - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return The received message, `NULL` on error - */ -const char *bus_poll_timed(bus_t *bus, const struct timespec *timeout, clockid_t clockid) -{ - int state = 0, saved_errno; - struct timespec delta; - if (!timeout) - return bus_poll(bus, 0); - - if (!bus->first_poll) { - t(release_semaphore(bus, W, SEM_UNDO)); state++; - t(acquire_semaphore(bus, S, SEM_UNDO)); state++; - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER - t(zero_semaphore(bus, N, 0)); -#endif - t(release_semaphore(bus, S, SEM_UNDO)); state--; - t(acquire_semaphore(bus, W, SEM_UNDO)); state--; - t(release_semaphore(bus, Q, 0)); - } else { - bus->first_poll = 0; - } - state--; - DELTA; - t(zero_semaphore_timed(bus, Q, 0, &delta)); - return bus->message; - -fail: - saved_errno = errno; - if (state > 1) - release_semaphore(bus, S, SEM_UNDO); - if (state > 0) - acquire_semaphore(bus, W, SEM_UNDO); - if (state < 0) - bus->first_poll = 1; - errno = saved_errno; - return NULL; -} - - -/** - * Change the ownership of a bus - * - * `stat(2)` can be used of the bus's associated file to get the bus's ownership - * - * @param file The pathname of the bus - * @param owner The user ID of the bus's new owner - * @param group The group ID of the bus's new group - * @return 0 on success, -1 on error - */ -int -bus_chown(const char *file, uid_t owner, gid_t group) -{ - bus_t bus; - struct semid_ds sem_stat; - struct shmid_ds shm_stat; - int shm_id; - - t(bus_open(&bus, file, -1)); - t(chown(file, owner, group)); - - /* chown sem */ - t(open_semaphores(&bus)); - t(semctl(bus.sem_id, 0, IPC_STAT, &sem_stat)); - sem_stat.sem_perm.uid = owner; - sem_stat.sem_perm.gid = group; - t(semctl(bus.sem_id, 0, IPC_SET, &sem_stat)); - - /* chown shm */ - t(shm_id = shmget(bus.key_shm, (size_t)BUS_MEMORY_SIZE, 0)); - t(shmctl(shm_id, IPC_STAT, &shm_stat)); - shm_stat.shm_perm.uid = owner; - shm_stat.shm_perm.gid = group; - t(shmctl(shm_id, IPC_SET, &shm_stat)); - - return 0; -fail: - return -1; -} - - -/** - * Change the permissions for a bus - * - * `stat(2)` can be used of the bus's associated file to get the bus's permissions - * - * @param file The pathname of the bus - * @param mode The permissions of the bus, any permission for a user implies - * full permissions for that user, except only the owner may - * edit the bus's associated file - * @return 0 on success, -1 on error - */ -int -bus_chmod(const char *file, mode_t mode) -{ - bus_t bus; - mode_t fmode; - struct semid_ds sem_stat; - struct shmid_ds shm_stat; - int shm_id; - - mode = (mode & S_IRWXU) ? (mode | S_IRWXU) : (mode & (mode_t)~S_IRWXU); - mode = (mode & S_IRWXG) ? (mode | S_IRWXG) : (mode & (mode_t)~S_IRWXG); - mode = (mode & S_IRWXO) ? (mode | S_IRWXO) : (mode & (mode_t)~S_IRWXO); - mode &= (S_IWUSR | S_IWGRP | S_IWOTH | S_IRUSR | S_IRGRP | S_IROTH); - fmode = mode & (mode_t)~(S_IWGRP | S_IWOTH); - - t(bus_open(&bus, file, -1)); - t(chmod(file, fmode)); - - /* chmod sem */ - t(open_semaphores(&bus)); - t(semctl(bus.sem_id, 0, IPC_STAT, &sem_stat)); - sem_stat.sem_perm.mode = (unsigned short)mode; - t(semctl(bus.sem_id, 0, IPC_SET, &sem_stat)); - - /* chmod shm */ - t(shm_id = shmget(bus.key_shm, (size_t)BUS_MEMORY_SIZE, 0)); - t(shmctl(shm_id, IPC_STAT, &shm_stat)); - shm_stat.shm_perm.mode = (unsigned short)mode; - t(shmctl(shm_id, IPC_SET, &shm_stat)); - - return 0; -fail: - return -1; -} - diff --git a/src/bus.h b/src/bus.h deleted file mode 100644 index ff2f1fb..0000000 --- a/src/bus.h +++ /dev/null @@ -1,347 +0,0 @@ -/** - * MIT/X Consortium License - * - * Copyright © 2015 Mattias Andrée - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#ifndef BUS_H -#define BUS_H - - -#ifndef _DEFAULT_SOURCE -# define _DEFAULT_SOURCE -#endif -#include -#include - - - -#if defined(__GNUC__) -# define BUS_COMPILER_GCC(X) X -#else -# define BUS_COMPILER_GCC(X) /* ignore */ -#endif - - - -/** - * Open the bus for reading only - */ -#define BUS_RDONLY 1 - -/** - * Open the bus for writing only - */ -#define BUS_WRONLY 0 - -/** - * Open the bus for both reading and writing only - */ -#define BUS_RDWR 0 - -/** - * Fail to create bus if its file already exists - */ -#define BUS_EXCL 2 - -/** - * Fail if interrupted - */ -#define BUS_INTR 4 - -/** - * Function shall fail with errno set to `EAGAIN` - * if the it would block and this flag is used - */ -#define BUS_NOWAIT 1 - - - -/** - * The number of bytes in storeable in the shared memory, - * note that this includes the NUL-termination. - * This means that message can be at most one byte smaller. - */ -#define BUS_MEMORY_SIZE 2048 - - - -/** - * Bus information - */ -typedef struct bus -{ - /** - * The key for the semaphore array - */ - key_t key_sem; - - /** - * The key for the shared memory - */ - key_t key_shm; - - /** - * The ID of the semaphore array - */ - int sem_id; - - /** - * The address of the shared memory - */ - char *message; - - /** - * Non-zero if and only if `bus_poll` has not been - * called since the last `bus_poll_start`, or - * if `bus_poll` failed during reading - */ - int first_poll; - -} bus_t; - - - -/** - * Create a new bus - * - * @param file The pathname of the bus, `NULL` to create a random one - * @param flags `BUS_EXCL` (if `file` is not `NULL`) to fail if the file - * already exists, otherwise if the file exists, nothing - * will happen; - * `BUS_INTR` to fail if interrupted - * @param out_file Output parameter for the pathname of the bus - * @return 0 on success, -1 on error - */ -int bus_create(const char *restrict, int, char **restrict) - BUS_COMPILER_GCC(__attribute__((__warn_unused_result__))); - -/** - * Remove a bus - * - * @param file The pathname of the bus - * @return 0 on success, -1 on error - */ -int bus_unlink(const char *) - BUS_COMPILER_GCC(__attribute__((__nonnull__))); - - -/** - * Open an existing bus - * - * @param bus Bus information to fill - * @param file The filename of the bus - * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR`, - * the value must not be negative - * @return 0 on success, -1 on error - */ -int bus_open(bus_t *restrict, const char *restrict, int) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Close a bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int bus_close(bus_t *) - BUS_COMPILER_GCC(__attribute__((__nonnull__))); - - -/** - * Broadcast a message on a bus - * - * @param bus Bus information - * @param message The message to write, may not be longer than - * `BUS_MEMORY_SIZE` including the NUL-termination - * @param flags `BUS_NOWAIT` if this function shall fail if - * another process is currently running this - * procedure - * @return 0 on success, -1 on error - */ -int bus_write(const bus_t *, const char *, int) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Broadcast a message on a bus - * - * @param bus Bus information - * @param message The message to write, may not be longer than - * `BUS_MEMORY_SIZE` including the NUL-termination - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return 0 on success, -1 on error - */ -int bus_write_timed(const bus_t *, const char *, const struct timespec *, clockid_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))); - - -/** - * Listen (in a loop, forever) for new message on a bus - * - * @param bus Bus information - * @param callback Function to call when a message is received, the - * (message, user_data) input parameters will be the read message and - * `user_data` from `bus_read`'s parameter with the - * same name. The message must have been parsed or - * copied when `callback` returns as it may be over - * overridden after that time. `callback` should - * return either of the the values: - * * 0: stop listening - * * 1: continue listening - * * -1: an error has occurred - * However, the function [`bus_read`] will invoke - * `callback` with `message` set to `NULL`one time - * directly after it has started listening on the - * bus. This is to the the program now it can safely - * continue with any action that requires that the - * programs is listening on the bus. - * @param user_data Parameter passed to `callback` - * @return 0 on success, -1 on error - */ -int bus_read(const bus_t *restrict, int (*)(const char *, void *), void *) - BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))); - -/** - * Listen (in a loop, forever) for new message on a bus - * - * @param bus Bus information - * @param callback Function to call when a message is received, the - * (message, user_data) input parameters will be the read message and - * `user_data` from `bus_read`'s parameter with the - * same name. The message must have been parsed or - * copied when `callback` returns as it may be over - * overridden after that time. `callback` should - * return either of the the values: - * * 0: stop listening - * * 1: continue listening - * * -1: an error has occurred - * However, the function [`bus_read`] will invoke - * `callback` with `message` set to `NULL`one time - * directly after it has started listening on the - * bus. This is to the the program now it can safely - * continue with any action that requires that the - * programs is listening on the bus. - * @param user_data Parameter passed to `callback` - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed, note that the callback - * function may or may not have been called - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return 0 on success, -1 on error - */ -int bus_read_timed(const bus_t *restrict, int (*)(const char *, void *), - void *, const struct timespec *, clockid_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))); - - -/** - * Announce that the thread is listening on the bus. - * This is required so the will does not miss any - * messages due to race conditions. Additionally, - * not calling this function will cause the bus the - * misbehave, is `bus_poll` is written to expect - * this function to have been called. - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int bus_poll_start(bus_t *) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Announce that the thread has stopped listening on the bus. - * This is required so that the thread does not cause others - * to wait indefinitely. - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int bus_poll_stop(const bus_t *) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Wait for a message to be broadcasted on the bus. - * The caller should make a copy of the received message, - * without freeing the original copy, and parse it in a - * separate thread. When the new thread has started be - * started, the caller of this function should then - * either call `bus_poll` again or `bus_poll_stop`. - * - * @param bus Bus information - * @param flags `BUS_NOWAIT` if the bus should fail and set `errno` to - * `EAGAIN` if there isn't already a message available on the bus - * @return The received message, `NULL` on error - */ -const char *bus_poll(bus_t *, int) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Wait for a message to be broadcasted on the bus. - * The caller should make a copy of the received message, - * without freeing the original copy, and parse it in a - * separate thread. When the new thread has started be - * started, the caller of this function should then - * either call `bus_poll_timed` again or `bus_poll_stop`. - * - * @param bus Bus information - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return The received message, `NULL` on error - */ -const char *bus_poll_timed(bus_t *, const struct timespec *, clockid_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__(1), __warn_unused_result__))); - - -/** - * Change the ownership of a bus - * - * `stat(2)` can be used of the bus's associated file to get the bus's ownership - * - * @param file The pathname of the bus - * @param owner The user ID of the bus's new owner - * @param group The group ID of the bus's new group - * @return 0 on success, -1 on error - */ -int bus_chown(const char *, uid_t, gid_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Change the permissions for a bus - * - * `stat(2)` can be used of the bus's associated file to get the bus's permissions - * - * @param file The pathname of the bus - * @param mode The permissions of the bus, any permission for a user implies - * full permissions for that user, except only the owner may - * edit the bus's associated file - * @return 0 on success, -1 on error - */ -int bus_chmod(const char *, mode_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - - - -#endif - diff --git a/src/cmdline.c b/src/cmdline.c deleted file mode 100644 index 25939cb..0000000 --- a/src/cmdline.c +++ /dev/null @@ -1,429 +0,0 @@ -/** - * MIT/X Consortium License - * - * Copyright © 2015 Mattias Andrée - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#include "bus.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - - -/** - * Statement wrapper that goes to `fail` on failure - */ -#define t(inst) if ((inst) == -1) goto fail - - - -/** - * The name of the process - */ -char *argv0; - -/** - * The command to spawn when a message is received - */ -static const char *command; - - - -/** - * Spawn a command because a message has been received - * - * @param message The received message - * @param user_data Not used - * @return 1 (continue listening) on success, -1 on error - */ -static int -spawn_continue(const char *message, void *user_data) -{ - pid_t pid; - if (!message) - return 1; - if ((pid = fork())) - return pid == -1 ? -1 : 1; - setenv("msg", message, 1); - execlp("sh", "sh", "-c", command, NULL); - perror(argv0); - exit(1); - (void) user_data; -} - - -/** - * Spawn a command because a message has been received - * - * @param message The received message - * @param user_data Not used - * @return 0 (stop listening) on success, -1 on error, or 1 if `message` is `NULL` - */ -static int -spawn_break(const char *message, void *user_data) -{ - pid_t pid; - if (!message) - return 1; - if ((pid = fork())) - return pid == -1 ? -1 : 0; - setenv("msg", message, 1); - execlp("sh", "sh", "-c", command, NULL); - perror(argv0); - exit(1); - (void) user_data; -} - - -/** - * Parse a permission string - * - * @param str The permission string - * @param andnot Output paramter for the mask of bits to remove (before applying `*or`) - * @param or Output paramter for the mask of bits to apply - * @return 0 on success, -1 on error - */ -static int -parse_mode(const char *str, mode_t *andnot, mode_t *or) -{ -#define U S_IRWXU -#define G S_IRWXG -#define O S_IRWXO - const char *s = str; - int numerical = 1; - mode_t mode = 0; - char op = '='; - mode_t bits; - - *andnot = 0; - *or = 0; - - if (!*s) - return errno = 0, -1; - - for (s = str; *s; s++) { - if (('0' > *s) || (*s > '7')) { - numerical = 0; - break; - } else { - mode = (mode << 3) | (*s & 15); - } - } - - if (numerical) { - *andnot = U | G | O; - *or = mode; - *or &= U | G | O; - *or = (*or & U) ? (*or | U) : (*or & (mode_t)~U); - *or = (*or & G) ? (*or | G) : (*or & (mode_t)~G); - *or = (*or & O) ? (*or | O) : (*or & (mode_t)~O); - return 0; - } - - for (s = str; *s; s++) { - if (strchr("+-=", *s)) { - op = *s; - } else if (strchr("ugo", *s)) { - if (*s == 'u') - bits = U; - else if (*s == 'g') - bits = G; - else - bits = O; - if (op == '+') { - *andnot |= bits; - *or |= bits; - } - else if (op == '-') { - *andnot |= bits; - *or &= ~bits; - } - else if (op == '=') { - *andnot |= U | G | O; - *or |= bits; - } - } else { - return errno = 0, -1; - } - } - - return 0; -} - - -/** - * Parse a user name/identifier string - * - * @param str The user's name or identifier - * @param uid Output parameter for the user's identifier - * @return 0 on success, -1 on error - */ -static int -parse_uid(const char *str, uid_t *uid) -{ - const char *s = str; - int numerical = 1; - uid_t rc = 0; - struct passwd *pwd; - - if (!*s || (*s == ':')) - return errno = 0, -1; - - for (s = str; *s; s++) { - if (('0' > *s) || (*s > '9')) { - numerical = 0; - break; - } - } - - if (numerical) { - for (s = str; *s; s++) - rc = (rc * 10) + (*s & 15); - *uid = rc; - return 0; - } - - pwd = getpwnam(str); - if (!pwd) { - return -1; - } - *uid = pwd->pw_uid; - return 0; -} - - -/** - * Parse a group name/identifier string - * - * @param str The group's name or identifier - * @param gid Output parameter for the group's identifier - * @return 0 on success, -1 on error - */ -static int -parse_gid(const char *str, gid_t *gid) -{ - const char *s = str; - int numerical = 1; - gid_t rc = 0; - struct group *grp; - - if (!*s || strchr(s, ':')) - return errno = 0, -1; - - for (s = str; *s; s++) { - if (('0' > *s) || (*s > '9')) { - numerical = 0; - break; - } - } - - if (numerical) { - for (s = str; *s; s++) - rc = (rc * 10) + (*s & 15); - *gid = rc; - return 0; - } - - grp = getgrnam(str); - if (!grp) - return -1; - *gid = grp->gr_gid; - return 0; -} - - -/** - * Parse a ownership string - * - * @param str The ownership string - * @param uid Output parameter for the owner, `NULL` if `str` only contains the group - * @param gid Output parameter for the group, `NULL` if `str` only contains the owner - * @return 0 on success, -1 on error - */ -static int -parse_owner(char *str, uid_t *uid, gid_t *gid) -{ - int r = 0; - char* group; - - if (!uid) - return parse_gid(str, gid); - if (!gid) - return parse_uid(str, uid); - - group = strchr(str, ':'); - *group++ = 0; - - r = parse_gid(group, gid); - if (r) - return r; - return parse_uid(str, uid); -} - - - -/** - * Main function of the command line interface for the bus system - * - * @param argc The number of elements in `argv` - * @param argv The command. Valid commands: - * create [-x] [--] [] # create a bus - * remove [--] # remove a bus - * listen [--] # listen for new messages - * wait [--] # listen for one new message - * broadcast [-n] [--] # broadcast a message - * chmod [--] # change permissions - * chown [--] [:] # change ownership - * chgrp [--] # change group - * will be spawned with $arg set to the message - * @return 0 on sucess, 1 on error, 2 on invalid command - */ -int -main(int argc, char *argv[]) -{ - bus_t bus; - char *file; - struct stat attr; - uid_t uid; - gid_t gid; - mode_t mode_andnot, mode_or; - int opt_x = 0, opt_n = 0; - const char *arg; - char **nonoptv = alloca((size_t)argc * sizeof(char*)); - int nonoptc = 0; - - argv0 = *argv++; - argc--; - - /* Parse arguments. */ - while (argc) { - if (!strcmp(*argv, "--")) { - argv++; - argc--; - break; - } else if (**argv == '-') { - arg = *argv++; - argc--; - for (arg++; *arg; arg++) { - if (*arg == 'x') - opt_x = 1; - else if (*arg == 'n') - opt_n = 1; - else - return -2; - } - } else { - *nonoptv++ = *argv++; - nonoptc++; - argc--; - } - } - while (argc) { - *nonoptv++ = *argv++; - nonoptc++; - argc--; - } - nonoptv -= nonoptc; - - /* Check options. */ - if (opt_x && strcmp(nonoptv[0], "create") && (nonoptc != 2)) - return 2; - if (opt_n && strcmp(nonoptv[0], "broadcast") && (nonoptc != 3)) - return 2; - - /* Create a new bus with selected name. */ - if ((nonoptc == 2) && !strcmp(nonoptv[0], "create")) { - t(bus_create(nonoptv[1], opt_x * BUS_EXCL, NULL)); - - /* Create a new bus with random name. */ - } else if ((nonoptc == 1) && !strcmp(nonoptv[0], "create")) { - t(bus_create(NULL, 0, &file)); - printf("%s\n", file); - free(file); - - /* Remove a bus. */ - } else if ((nonoptc == 2) && !strcmp(nonoptv[0], "remove")) { - t(bus_unlink(nonoptv[1])); - - /* Listen on a bus in a loop. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "listen")) { - command = nonoptv[2]; - t(bus_open(&bus, nonoptv[1], BUS_RDONLY)); - t(bus_read(&bus, spawn_continue, NULL)); - t(bus_close(&bus)); - - /* Listen on a bus for one message. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "wait")) { - command = nonoptv[2]; - t(bus_open(&bus, nonoptv[1], BUS_RDONLY)); - t(bus_read(&bus, spawn_break, NULL)); - t(bus_close(&bus)); - - /* Broadcast a message on a bus. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "broadcast")) { - t(bus_open(&bus, nonoptv[1], BUS_WRONLY)); - t(bus_write(&bus, nonoptv[2], opt_n * BUS_NOWAIT)); - t(bus_close(&bus)); - - /* Change permissions. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "chmod")) { - t(parse_mode(nonoptv[1], &mode_andnot, &mode_or)); - t(stat(nonoptv[2], &attr)); - attr.st_mode &= ~mode_andnot; - attr.st_mode |= mode_or; - t(bus_chmod(nonoptv[2], attr.st_mode)); - - /* Change ownership. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "chown")) { - if (strchr(nonoptv[1], ':')) { - t(parse_owner(nonoptv[1], &uid, &gid)); - t(bus_chown(nonoptv[2], uid, gid)); - } else { - t(parse_owner(nonoptv[1], &uid, NULL)); - t(stat(nonoptv[2], &attr)); - t(bus_chown(nonoptv[2], uid, attr.st_gid)); - } - - /* Change group. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "chgrp")) { - t(parse_owner(nonoptv[1], NULL, &gid)); - t(stat(nonoptv[2], &attr)); - t(bus_chown(nonoptv[2], attr.st_uid, gid)); - - } else - return 2; - - return 0; - -fail: - if (errno == 0) - return 2; - perror(argv0); - return 1; -} - -- cgit v1.2.3-70-g09d2