├── .gitignore ├── LICENSE ├── Makefile ├── README ├── arg.h ├── bus-broadcast.1 ├── bus-chgrp.1 ├── bus-chmod.1 ├── bus-chown.1 ├── bus-create.1 ├── bus-listen.1 ├── bus-remove.1 ├── bus-wait.1 ├── bus.1 ├── bus.5 ├── bus.c ├── bus.h ├── bus.texinfo ├── bus_chmod.3 ├── bus_chown.3 ├── bus_close.3 ├── bus_create.3 ├── bus_open.3 ├── bus_poll.3 ├── bus_read.3 ├── bus_unlink.3 ├── bus_write.3 ├── config.mk ├── doc ├── examples │ ├── audio-volume-control │ │ ├── README │ │ ├── amixer │ │ ├── cleanup │ │ ├── init │ │ └── monitor │ ├── daemon-dependencies │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── README │ │ ├── announce.c │ │ ├── await-ready.c │ │ ├── await-started.c │ │ ├── cleanup.c │ │ ├── d-network │ │ ├── d-ntp │ │ ├── d-ssh │ │ ├── init.c │ │ ├── require.c │ │ ├── start-daemon.c │ │ └── test-daemon.c │ ├── nonblocking │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── README │ │ ├── cleanup.c │ │ ├── init.c │ │ ├── poll.c │ │ └── write.c │ ├── telephony-and-music │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── README │ │ ├── cleanup.c │ │ ├── end-call.c │ │ ├── init.c │ │ ├── monitor.c │ │ └── receive-or-make-call.c │ └── timed │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── README │ │ ├── cleanup.c │ │ ├── init.c │ │ ├── poll.c │ │ ├── read.c │ │ ├── slow-poll.c │ │ └── write.c └── protocol ├── fdl.texinfo ├── libbus.7 └── libbus.c /.gitignore: -------------------------------------------------------------------------------- 1 | *\#* 2 | *~ 3 | *.o 4 | *.out 5 | *.su 6 | *.gch 7 | *.so 8 | *.a 9 | *.lo 10 | *.log 11 | *.toc 12 | *.aux 13 | *.pdf 14 | /bus 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | © 2015, 2017 Mattias Andrée 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .POSIX: 2 | 3 | CONFIGFILE = config.mk 4 | include $(CONFIGFILE) 5 | 6 | LIB_MAJOR = 3 7 | LIB_MINOR = 1 8 | LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR) 9 | VERSION = 3.1.7 10 | 11 | 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 12 | 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 13 | MAN5 = bus.5 14 | MAN7 = libbus.7 15 | 16 | LOBJ = libbus.lo 17 | OBJ = bus.o libbus.o 18 | HDR = bus.h arg.h 19 | 20 | all: bus libbus.a libbus.so 21 | 22 | $(OBJ): $(@:.o=.c) $(HDR) 23 | $(LOBJ): $(@:.lo=.c) $(HDR) 24 | 25 | bus: $(OBJ) 26 | $(CC) -o $@ $(OBJ) $(LDFLAGS) 27 | 28 | .o.a: 29 | $(AR) $(ARFLAGS) $@ $< 30 | 31 | .c.lo: 32 | $(CC) $(CFLAGS) -fPIC -c -o $@ $< 33 | 34 | .lo.so: 35 | $(CC) -shared -Wl,-soname,$@.$(LIB_MAJOR) -o $@ $< $(LDFLAGS) 36 | 37 | bus.pdf: bus.texinfo fdl.texinfo 38 | texi2pdf bus.texinfo < /dev/null 39 | 40 | install: bus libbus.a libbus.so 41 | mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" 42 | mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" 43 | mkdir -p -- "$(DESTDIR)$(PREFIX)/include" 44 | mkdir -p -- "$(DESTDIR)$(PREFIX)/share/licenses/bus" 45 | mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1" 46 | mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man3" 47 | mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man5" 48 | mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man7" 49 | cp -- bus "$(DESTDIR)$(PREFIX)/bin" 50 | cp -- libbus.a "$(DESTDIR)$(PREFIX)/lib" 51 | cp -- libbus.so "$(DESTDIR)$(PREFIX)/lib/libbus.so.$(LIB_VERSION)" 52 | cp -- bus.h "$(DESTDIR)$(PREFIX)/include" 53 | cp -- LICENSE "$(DESTDIR)$(PREFIX)/share/licenses/bus" 54 | ln -sf -- libbus.so.$(LIB_VERSION) "$(DESTDIR)$(PREFIX)/lib/libbus.so.$(LIB_MAJOR)" 55 | ln -sf -- libbus.so.$(LIB_VERSION) "$(DESTDIR)$(PREFIX)/lib/libbus.so" 56 | cp -- $(MAN1) "$(DESTDIR)$(MANPREFIX)/man1" 57 | cp -- $(MAN3) "$(DESTDIR)$(MANPREFIX)/man3" 58 | cp -- $(MAN5) "$(DESTDIR)$(MANPREFIX)/man5" 59 | cp -- $(MAN7) "$(DESTDIR)$(MANPREFIX)/man7" 60 | ln -sf -- bus_poll.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_start.3" 61 | ln -sf -- bus_poll.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_stop.3" 62 | ln -sf -- bus_poll.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_timed.3" 63 | ln -sf -- bus_read.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_read_timed.3" 64 | ln -sf -- bus_write.3 "$(DESTDIR)$(MANPREFIX)/man3/bus_write_timed.3" 65 | 66 | uninstall: 67 | -rm -f -- "$(DESTDIR)$(PREFIX)/bin/bus" 68 | -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libbus.a" 69 | -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libbus.so.$(LIB_VERSION)" 70 | -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libbus.so.$(LIB_MAJOR)" 71 | -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libbus.so" 72 | -rm -f -- "$(DESTDIR)$(PREFIX)/include/bus.h" 73 | -rm -rf -- "$(DESTDIR)$(PREFIX)/share/licenses/bus" 74 | -cd "$(DESTDIR)$(MANPREFIX)/man1" && rm -f -- $(MAN1) 75 | -cd "$(DESTDIR)$(MANPREFIX)/man3" && rm -f -- $(MAN3) 76 | -cd "$(DESTDIR)$(MANPREFIX)/man5" && rm -f -- $(MAN5) 77 | -cd "$(DESTDIR)$(MANPREFIX)/man7" && rm -f -- $(MAN7) 78 | -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_start.3" 79 | -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_stop.3" 80 | -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_poll_timed.3" 81 | -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_read_timed.3" 82 | -rm -f -- "$(DESTDIR)$(MANPREFIX)/man3/bus_write_timed.3" 83 | 84 | clean: 85 | -rm -f -- bus *.o *.lo *.a *.so *.log *.toc *.aux *.pdf 86 | 87 | .SUFFIXES: 88 | .SUFFIXES: .so .a .o .lo .c .pdf 89 | 90 | .PHONY: all install uninstall clean 91 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This repo has been moved to Codeberg and may be out of date on GitHub. 2 | Canonical repo: https://codeberg.org/maandree/bus 3 | 4 | 5 | NAME 6 | bus - A simple daemonless system for broadcasting messages locally 7 | 8 | DESCRIPTION 9 | bus is a simple daemonless system for broadcasting messages locally. 10 | It is a lightweight alternative to a two-phase interprocess flexible 11 | barrier. 12 | 13 | bus uses a System V semaphore array and System V shared memory. 14 | Buses are named; the key of the semaphore array and the shared 15 | memory is stored in a regular file. 16 | 17 | The shared memory used by bus is always 2048 bytes. Additionally 18 | all messages should be encoded in UTF-8 and not contain any NULL 19 | characters, except they MUST always end with a zero byte. 20 | Furthermore messages should be prefixed with the process 21 | identifer of the process whence the message originated, followed 22 | by a space. If the process is ephemeral, 0 should be used instead 23 | of the process identifier. 24 | 25 | Communication over bus is synchronous. The broadcast call does 26 | not return until all listeners have received (and copied) the 27 | message. A malfunctioning program can lock the bus. 28 | 29 | This software package contains a C library and a command line 30 | utility. The package python-bus provides a Python 3 module. 31 | 32 | RATIONALE 33 | We need an interprocess communication system similar to message 34 | queues. But we need broadcasting rather than anycasting, so we 35 | have a fast, simple and daemonless system for announcing events 36 | to any processes that might be interested. 37 | 38 | SEE ALSO 39 | libshr(7), cmdipc(1), msgctl(3), semctl(3), shmctl(3), mqueue.h(0), 40 | semaphore.h(0), sem_overview(7), mmap(3), pipe(3), socketpair(3), 41 | socket(3), unix(7), cmsg(3), kill(3), eventfd(2), memfd_create(2), 42 | pthread_mutex_destroy(3), pthread_rwlock_destroy(3), futex(7), 43 | pthread_spin_destroy(3), pthread_cond_destroy(3), lockf(3), flock(2), 44 | fcntl(3), ioctl(3), mkfifo(3), rendezvous(2), 9p(2), libdoor(3), 45 | python-bus 46 | -------------------------------------------------------------------------------- /arg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copy me if you can. 3 | * by 20h 4 | */ 5 | 6 | #ifndef ARG_H__ 7 | #define ARG_H__ 8 | 9 | extern char *argv0; 10 | 11 | /* use main(int argc, char *argv[]) */ 12 | #define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ 13 | argv[0] && argv[0][0] && argv[0][1];\ 14 | argc--, argv++) {\ 15 | char argc_;\ 16 | char **argv_;\ 17 | int brk_;\ 18 | if (argv[0][0] == '-') {\ 19 | if (argv[0][1] == '-' && argv[0][2] == '\0') {\ 20 | argv++;\ 21 | argc--;\ 22 | break;\ 23 | }\ 24 | for (brk_ = 0, argv[0]++, argv_ = argv;\ 25 | argv[0][0] && !brk_;\ 26 | argv[0]++) {\ 27 | if (argv_ != argv)\ 28 | break;\ 29 | argc_ = argv[0][0];\ 30 | switch (argc_) 31 | 32 | #define ARGEND }\ 33 | } else {\ 34 | break;\ 35 | }\ 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /bus-broadcast.1: -------------------------------------------------------------------------------- 1 | .TH BUS-BROADCAST 1 BUS 2 | .SH NAME 3 | bus broadcast - Broadcast a message on a bus 4 | .SH SYNOPSIS 5 | .B bus broadcast 6 | [-n] 7 | .IR pathname 8 | .IR message 9 | .SH DESCRIPTION 10 | Broadcast \fImessage\fP on the bus associated with \fIpathname\fP. 11 | .SH OPTIONS 12 | .TP 13 | .B \-n 14 | Fail if another process is attempting to broadcast on the bus. 15 | .SH EXIT STATUS 16 | .TP 17 | 0 18 | The command was successful. 19 | .TP 20 | 1 21 | The command failed. 22 | .TP 23 | 2 24 | The command is not recognised. 25 | .SH SEE ALSO 26 | .BR bus (5) 27 | -------------------------------------------------------------------------------- /bus-chgrp.1: -------------------------------------------------------------------------------- 1 | .TH BUS-CHGRP 1 BUS 2 | .SH NAME 3 | bus chgrp - Change group ownership of a bus 4 | .SH SYNOPSIS 5 | .B bus chgrp 6 | .IR group 7 | .IR pathname 8 | .SH DESCRIPTION 9 | Change the group, that owns a bus with an associated \fIpathname\fP, 10 | to the specified \fIgroup\fP. The \fIgroup\fP can be specified either 11 | with a GID or with a group name. 12 | .PP 13 | The current ownership of a bus can be retrieved by running 14 | .BR stat (1) 15 | over the \fIpathname\fP of the bus. 16 | .SH EXIT STATUS 17 | .TP 18 | 0 19 | The command was successful. 20 | .TP 21 | 1 22 | The command failed. 23 | .TP 24 | 2 25 | The command is not recognised. 26 | .SH SEE ALSO 27 | .BR bus-chown (1), 28 | .BR bus-chmod (1), 29 | .BR stat (1) 30 | -------------------------------------------------------------------------------- /bus-chmod.1: -------------------------------------------------------------------------------- 1 | .TH BUS-CHMOD 1 BUS 2 | .SH NAME 3 | bus chmod - Change permissions on a bus 4 | .SH SYNOPSIS 5 | .B bus chmod 6 | .IR permissions 7 | .IR pathname 8 | .SH DESCRIPTION 9 | Change who have access to a bus with an associated \fIpathname\fP. 10 | In the \fIpermissions\fP, the owner, the group, and others (not 11 | in group) are represented by the symbols \fBu\fP, \fBg\fP, and 12 | \fBo\fP, respectively. The \fIpermissions\fP string is imagined 13 | to have always be prefixed with an \fB=\fP. This symbols means 14 | that all user classes list after it, and only those classes, as 15 | permission to use the bus. Similarly the symbols \fB+\fP and 16 | \fB\-\fP can be used to grant and revoke access, respectively. 17 | The symbols \fB=\fP, \fB+\fP, and \fB\-\fP can be mixed, and are 18 | interpreted from left to right. Alternatively the \fIpermissions\fP 19 | string can be a octal number, where the owner is represented by any 20 | bit in 700 (100, 200, or 400, or any combination thereof), the 21 | group is represented by any bit in 70, and others (not in the group) 22 | is represented by any bit in 7. 23 | .PP 24 | The current permissions of a bus can be retrieved by running 25 | .BR stat (1) 26 | over the \fIpathname\fP of the bus. 27 | .SH EXIT STATUS 28 | .TP 29 | 0 30 | The command was successful. 31 | .TP 32 | 1 33 | The command failed. 34 | .TP 35 | 2 36 | The command is not recognised. 37 | .SH SEE ALSO 38 | .BR bus-chown (1), 39 | .BR bus-chgrp (1), 40 | .BR stat (1) 41 | -------------------------------------------------------------------------------- /bus-chown.1: -------------------------------------------------------------------------------- 1 | .TH BUS-CHOWN 1 BUS 2 | .SH NAME 3 | bus chown - Change ownership of a bus 4 | .SH SYNOPSIS 5 | .B bus chown 6 | .IR owner [\fB:\fP group ] 7 | .IR pathname 8 | .SH DESCRIPTION 9 | Change the owner, that owns a bus with an associated \fIpathname\fP, 10 | to the specified \fIowner\fP. The \fIowner\fP can be specified either 11 | with a UID or with a user name. If a \fIgroup\fP is specified, the 12 | bus's owner-group will be set to that \fIgroup\fP, otherwise the group 13 | will remain unchanged. The \fIgroup\fP can be specified either with 14 | a GID or with a group name. 15 | .PP 16 | The current ownership of a bus can be retrieved by running 17 | .BR stat (1) 18 | over the \fIpathname\fP of the bus. 19 | .SH EXIT STATUS 20 | .TP 21 | 0 22 | The command was successful. 23 | .TP 24 | 1 25 | The command failed. 26 | .TP 27 | 2 28 | The command is not recognised. 29 | .SH SEE ALSO 30 | .BR bus-chgrp (1), 31 | .BR bus-chmod (1), 32 | .BR stat (1) 33 | -------------------------------------------------------------------------------- /bus-create.1: -------------------------------------------------------------------------------- 1 | .TH BUS-CREATE 1 BUS 2 | .SH NAME 3 | bus create - Create a bus 4 | .SH SYNOPSIS 5 | .B bus create 6 | [-x] 7 | .IR [pathname] 8 | .SH DESCRIPTION 9 | Create a bus with an associated \fIpathname\fP. If \fIpathname\fP 10 | is omitted, a random pathname in \fI$XDG_RUNTIME_DIR/bus\fP will be 11 | created and printed to stdout. 12 | .SH OPTIONS 13 | .TP 14 | .B \-x 15 | Fail if the \fIpathname\fP already exists. 16 | .SH EXIT STATUS 17 | .TP 18 | 0 19 | The command was successful. 20 | .TP 21 | 1 22 | The command failed. 23 | .TP 24 | 2 25 | The command is not recognised. 26 | .SH SEE ALSO 27 | .BR bus (5), 28 | .BR bus-remove (1) 29 | -------------------------------------------------------------------------------- /bus-listen.1: -------------------------------------------------------------------------------- 1 | .TH BUS-LISTEN 1 BUS 2 | .SH NAME 3 | bus listen - Listen for new messages on a bus 4 | .SH SYNOPSIS 5 | .B bus listen 6 | .IR pathname 7 | .IR command 8 | .SH DESCRIPTION 9 | Listen for new messages on the bus associated with \fIpathname\fP. Once 10 | a message is received, \fIcommand\fP will be spawned with \fI$msg\fP set 11 | to the received message. POSIX shell syntax applies to \fIcommand\fP. 12 | .SH EXIT STATUS 13 | .TP 14 | 0 15 | The command was successful. 16 | .TP 17 | 1 18 | The command failed. 19 | .TP 20 | 2 21 | The command is not recognised. 22 | .SH SEE ALSO 23 | .BR bus (5) 24 | -------------------------------------------------------------------------------- /bus-remove.1: -------------------------------------------------------------------------------- 1 | .TH BUS-REMOVE 1 BUS 2 | .SH NAME 3 | bus remove - Remove a bus 4 | .SH SYNOPSIS 5 | .B bus remove 6 | .IR pathname 7 | .SH DESCRIPTION 8 | Remove the bus associated with \fIpathname\fP. 9 | .SH EXIT STATUS 10 | .TP 11 | 0 12 | The command was successful. 13 | .TP 14 | 1 15 | The command failed. 16 | .TP 17 | 2 18 | The command is not recognised. 19 | .SH SEE ALSO 20 | .BR bus (5) 21 | -------------------------------------------------------------------------------- /bus-wait.1: -------------------------------------------------------------------------------- 1 | .TH BUS-WAIT 1 BUS 2 | .SH NAME 3 | bus wait - Listen for a new message on a bus 4 | .SH SYNOPSIS 5 | .B bus wait 6 | .IR pathname 7 | .IR command 8 | .SH DESCRIPTION 9 | Listen for a new message on the bus associated with \fIpathname\fP, stop 10 | listening once a message has been received. Once a message is received, 11 | \fIcommand\fP will be spawned with \fI$msg\fP set to the received 12 | message. POSIX shell syntax applies to \fIcommand\fP. 13 | .SH EXIT STATUS 14 | .TP 15 | 0 16 | The command was successful. 17 | .TP 18 | 1 19 | The command failed. 20 | .TP 21 | 2 22 | The command is not recognised. 23 | .SH SEE ALSO 24 | .BR bus (5) 25 | -------------------------------------------------------------------------------- /bus.1: -------------------------------------------------------------------------------- 1 | .TH BUS 1 BUS 2 | .SH NAME 3 | bus - A simple daemonless system for broadcasting messages locally 4 | .SH SYNOPSIS 5 | .B bus 6 | .IR command 7 | .IR argument\ ... 8 | .SH COMMANDS 9 | .TP 10 | .B create 11 | Create a bus, see 12 | .BR bus-create (1) 13 | for further details. 14 | .TP 15 | .B remove 16 | Remove a bus, see 17 | .BR bus-remove (1) 18 | for further details. 19 | .TP 20 | .B listen 21 | Listen for new message on a bus, see 22 | .BR bus-listen (1) 23 | for further details. 24 | .TP 25 | .B wait 26 | Listen for one new message only on a bus, see 27 | .BR bus-wait (1) 28 | for further details. 29 | .TP 30 | .B broadcast 31 | Broadcast a message on a bus, see 32 | .BR bus-broadcast (1) 33 | for further details. 34 | .TP 35 | .B chmod 36 | Change permissions on a bus, see 37 | .BR bus-chmod (1) 38 | for further details. 39 | .TP 40 | .B chown 41 | Change ownership of a bus, see 42 | .BR bus-chown (1) 43 | for further details. 44 | .TP 45 | .B chgrp 46 | Change group ownership of a bus, see 47 | .BR bus-chgrp (1) 48 | for further details. 49 | .SH EXIT STATUS 50 | .TP 51 | 0 52 | The command was successful. 53 | .TP 54 | 1 55 | The command failed. 56 | .TP 57 | 2 58 | The command is not recognised. 59 | .SH SEE ALSO 60 | .BR bus-create (1), 61 | .BR bus-remove (1), 62 | .BR bus-listen (1), 63 | .BR bus-wait (1), 64 | .BR bus-broadcast (1), 65 | .BR bus-chmod (1), 66 | .BR bus-chown (1), 67 | .BR bus-chgrp (1), 68 | .BR bus (5), 69 | .BR libbus (7) 70 | -------------------------------------------------------------------------------- /bus.5: -------------------------------------------------------------------------------- 1 | .TH BUS 5 BUS 2 | .SH NAME 3 | bus - A simple daemonless system for broadcasting messages locally 4 | .SH DESCRIPTION 5 | \fBbus\fP is a simple interprocess communication system for broadcasting 6 | messages to other processes on the same machine. \fBbus\fP does not use 7 | any daemon. Instead, all communication and synchronisation is managed 8 | using System V (XSI) semaphores and System V (XSI) shared memory. 9 | .PP 10 | The command \fBbus create\fP can be used to create new buses. By 11 | convention, buses should be stored in \fI$XDG_RUNTIME_DIR/bus\fP, this is 12 | what \fBbus create\fP does if no pathname is given. The pathname of the 13 | bus should be tracked using \fIBUS_X\fP, where \fIX\fP is replaced with 14 | either: 15 | .TP 16 | .B GENERIC 17 | For the bus used in generic cases. That is all but the cases of the 18 | buses listed below. 19 | .TP 20 | .B AUDIO 21 | For the bus used in with the audio subsystem is involved. 22 | .TP 23 | .B VIDEO 24 | For the bus used in with the video subsystem is involved. 25 | .TP 26 | .B INPUT 27 | For the bus used in with the input subsystem is involved. 28 | .TP 29 | .B FILES 30 | For the bus used in with the storage subsystem is involved. 31 | .PP 32 | Messages broadcasted on a bus cannot be longer than 2047 bytes, 33 | excluding NULL termination. Message should be encoded in UTF-8, 34 | and most not contain the NULL character. 35 | .PP 36 | Broadcasted message should start with the process ID, or 0 if ephemeral, 37 | whence the message originated, followed by a single regular space. 38 | .SH SEE ALSO 39 | .BR bus (1), 40 | .BR libbus (7), 41 | .BR semop (2), 42 | .BR shmop (2) 43 | -------------------------------------------------------------------------------- /bus.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include "bus.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "arg.h" 15 | 16 | 17 | 18 | /** 19 | * Statement wrapper that goes to `fail` on failure 20 | */ 21 | #define t(inst) do { if ((inst) == -1) goto fail; } while (0) 22 | 23 | 24 | 25 | /** 26 | * The name of the process 27 | */ 28 | char *argv0; 29 | 30 | /** 31 | * The command to spawn when a message is received 32 | */ 33 | static const char *command; 34 | 35 | 36 | 37 | /** 38 | * Spawn a command because a message has been received 39 | * 40 | * @param message The received message 41 | * @param user_data Not used 42 | * @return 1 (continue listening) on success, -1 on error 43 | */ 44 | static int 45 | spawn_continue(const char *message, void *user_data) 46 | { 47 | pid_t pid; 48 | if (!message) 49 | return 1; 50 | if ((pid = fork())) 51 | return pid == -1 ? -1 : 1; 52 | setenv("msg", message, 1); 53 | execlp("sh", "sh", "-c", command, NULL); 54 | perror(argv0); 55 | exit(1); 56 | (void) user_data; 57 | } 58 | 59 | 60 | /** 61 | * Spawn a command because a message has been received 62 | * 63 | * @param message The received message 64 | * @param user_data Not used 65 | * @return 0 (stop listening) on success, -1 on error, or 1 if `message` is `NULL` 66 | */ 67 | static int 68 | spawn_break(const char *message, void *user_data) 69 | { 70 | pid_t pid; 71 | if (!message) 72 | return 1; 73 | if ((pid = fork())) 74 | return pid == -1 ? -1 : 0; 75 | setenv("msg", message, 1); 76 | execlp("sh", "sh", "-c", command, NULL); 77 | perror(argv0); 78 | exit(1); 79 | (void) user_data; 80 | } 81 | 82 | 83 | /** 84 | * Parse a permission string 85 | * 86 | * @param str The permission string 87 | * @param andnot Output paramter for the mask of bits to remove (before applying `*or`) 88 | * @param or Output paramter for the mask of bits to apply 89 | * @return 0 on success, -1 on error 90 | */ 91 | static int 92 | parse_mode(const char *str, mode_t *andnot, mode_t *or) 93 | { 94 | #define U S_IRWXU 95 | #define G S_IRWXG 96 | #define O S_IRWXO 97 | const char *s = str; 98 | int numerical = 1; 99 | mode_t mode = 0; 100 | char op = '='; 101 | mode_t bits; 102 | 103 | *andnot = 0; 104 | *or = 0; 105 | 106 | if (!*s) 107 | return errno = 0, -1; 108 | 109 | for (s = str; *s; s++) { 110 | if (('0' > *s) || (*s > '7')) { 111 | numerical = 0; 112 | break; 113 | } else { 114 | mode = (mode << 3) | (*s & 15); 115 | } 116 | } 117 | 118 | if (numerical) { 119 | *andnot = U | G | O; 120 | *or = mode; 121 | *or &= U | G | O; 122 | *or = (*or & U) ? (*or | U) : (*or & (mode_t)~U); 123 | *or = (*or & G) ? (*or | G) : (*or & (mode_t)~G); 124 | *or = (*or & O) ? (*or | O) : (*or & (mode_t)~O); 125 | return 0; 126 | } 127 | 128 | for (s = str; *s; s++) { 129 | if (strchr("+-=", *s)) { 130 | op = *s; 131 | } else if (strchr("ugo", *s)) { 132 | if (*s == 'u') 133 | bits = U; 134 | else if (*s == 'g') 135 | bits = G; 136 | else 137 | bits = O; 138 | if (op == '+') { 139 | *andnot |= bits; 140 | *or |= bits; 141 | } 142 | else if (op == '-') { 143 | *andnot |= bits; 144 | *or &= ~bits; 145 | } 146 | else if (op == '=') { 147 | *andnot |= U | G | O; 148 | *or |= bits; 149 | } 150 | } else { 151 | return errno = 0, -1; 152 | } 153 | } 154 | 155 | return 0; 156 | } 157 | 158 | 159 | /** 160 | * Parse a user name/identifier string 161 | * 162 | * @param str The user's name or identifier 163 | * @param uid Output parameter for the user's identifier 164 | * @return 0 on success, -1 on error 165 | */ 166 | static int 167 | parse_uid(const char *str, uid_t *uid) 168 | { 169 | const char *s = str; 170 | int numerical = 1; 171 | uid_t rc = 0; 172 | struct passwd *pwd; 173 | 174 | if (!*s || (*s == ':')) 175 | return errno = 0, -1; 176 | 177 | for (s = str; *s; s++) { 178 | if (('0' > *s) || (*s > '9')) { 179 | numerical = 0; 180 | break; 181 | } 182 | } 183 | 184 | if (numerical) { 185 | for (s = str; *s; s++) 186 | rc = (rc * 10) + (*s & 15); 187 | *uid = rc; 188 | return 0; 189 | } 190 | 191 | pwd = getpwnam(str); 192 | if (!pwd) { 193 | return -1; 194 | } 195 | *uid = pwd->pw_uid; 196 | return 0; 197 | } 198 | 199 | 200 | /** 201 | * Parse a group name/identifier string 202 | * 203 | * @param str The group's name or identifier 204 | * @param gid Output parameter for the group's identifier 205 | * @return 0 on success, -1 on error 206 | */ 207 | static int 208 | parse_gid(const char *str, gid_t *gid) 209 | { 210 | const char *s = str; 211 | int numerical = 1; 212 | gid_t rc = 0; 213 | struct group *grp; 214 | 215 | if (!*s || strchr(s, ':')) 216 | return errno = 0, -1; 217 | 218 | for (s = str; *s; s++) { 219 | if (('0' > *s) || (*s > '9')) { 220 | numerical = 0; 221 | break; 222 | } 223 | } 224 | 225 | if (numerical) { 226 | for (s = str; *s; s++) 227 | rc = (rc * 10) + (*s & 15); 228 | *gid = rc; 229 | return 0; 230 | } 231 | 232 | grp = getgrnam(str); 233 | if (!grp) 234 | return -1; 235 | *gid = grp->gr_gid; 236 | return 0; 237 | } 238 | 239 | 240 | /** 241 | * Parse a ownership string 242 | * 243 | * @param str The ownership string 244 | * @param uid Output parameter for the owner, `NULL` if `str` only contains the group 245 | * @param gid Output parameter for the group, `NULL` if `str` only contains the owner 246 | * @return 0 on success, -1 on error 247 | */ 248 | static int 249 | parse_owner(char *str, uid_t *uid, gid_t *gid) 250 | { 251 | int r = 0; 252 | char* group; 253 | 254 | if (!uid) 255 | return parse_gid(str, gid); 256 | if (!gid) 257 | return parse_uid(str, uid); 258 | 259 | group = strchr(str, ':'); 260 | *group++ = 0; 261 | 262 | r = parse_gid(group, gid); 263 | if (r) 264 | return r; 265 | return parse_uid(str, uid); 266 | } 267 | 268 | 269 | 270 | /** 271 | * Main function of the command line interface for the bus system 272 | * 273 | * @param argc The number of elements in `argv` 274 | * @param argv The command. Valid commands: 275 | * create [-x] [--] [] # create a bus 276 | * remove [--] # remove a bus 277 | * listen [--] # listen for new messages 278 | * wait [--] # listen for one new message 279 | * broadcast [-n] [--] # broadcast a message 280 | * chmod [--] # change permissions 281 | * chown [--] [:] # change ownership 282 | * chgrp [--] # change group 283 | * will be spawned with $arg set to the message 284 | * @return 0 on sucess, 1 on error, 2 on invalid command 285 | */ 286 | int 287 | main(int argc, char *argv[]) 288 | { 289 | int xflag = 0; 290 | int nflag = 0; 291 | bus_t bus; 292 | char *file; 293 | struct stat attr; 294 | uid_t uid; 295 | gid_t gid; 296 | mode_t mode_andnot, mode_or; 297 | 298 | /* Parse arguments. */ 299 | ARGBEGIN { 300 | case 'x': 301 | xflag = 1; 302 | break; 303 | case 'n': 304 | nflag = 1; 305 | break; 306 | default: 307 | return 2; 308 | } ARGEND; 309 | 310 | /* Check options. */ 311 | if (xflag && strcmp(argv[0], "create") && (argc != 2)) 312 | return 2; 313 | if (nflag && strcmp(argv[0], "broadcast") && (argc != 3)) 314 | return 2; 315 | 316 | /* Create a new bus with selected name. */ 317 | if ((argc == 2) && !strcmp(argv[0], "create")) { 318 | t(bus_create(argv[1], xflag * BUS_EXCL, NULL)); 319 | 320 | /* Create a new bus with random name. */ 321 | } else if ((argc == 1) && !strcmp(argv[0], "create")) { 322 | t(bus_create(NULL, 0, &file)); 323 | printf("%s\n", file); 324 | free(file); 325 | 326 | /* Remove a bus. */ 327 | } else if ((argc == 2) && !strcmp(argv[0], "remove")) { 328 | t(bus_unlink(argv[1])); 329 | 330 | /* Listen on a bus in a loop. */ 331 | } else if ((argc == 3) && !strcmp(argv[0], "listen")) { 332 | command = argv[2]; 333 | t(bus_open(&bus, argv[1], BUS_RDONLY)); 334 | t(bus_read(&bus, spawn_continue, NULL)); 335 | t(bus_close(&bus)); 336 | 337 | /* Listen on a bus for one message. */ 338 | } else if ((argc == 3) && !strcmp(argv[0], "wait")) { 339 | command = argv[2]; 340 | t(bus_open(&bus, argv[1], BUS_RDONLY)); 341 | t(bus_read(&bus, spawn_break, NULL)); 342 | t(bus_close(&bus)); 343 | 344 | /* Broadcast a message on a bus. */ 345 | } else if ((argc == 3) && !strcmp(argv[0], "broadcast")) { 346 | t(bus_open(&bus, argv[1], BUS_WRONLY)); 347 | t(bus_write(&bus, argv[2], nflag * BUS_NOWAIT)); 348 | t(bus_close(&bus)); 349 | 350 | /* Change permissions. */ 351 | } else if ((argc == 3) && !strcmp(argv[0], "chmod")) { 352 | t(parse_mode(argv[1], &mode_andnot, &mode_or)); 353 | t(stat(argv[2], &attr)); 354 | attr.st_mode &= ~mode_andnot; 355 | attr.st_mode |= mode_or; 356 | t(bus_chmod(argv[2], attr.st_mode)); 357 | 358 | /* Change ownership. */ 359 | } else if ((argc == 3) && !strcmp(argv[0], "chown")) { 360 | if (strchr(argv[1], ':')) { 361 | t(parse_owner(argv[1], &uid, &gid)); 362 | t(bus_chown(argv[2], uid, gid)); 363 | } else { 364 | t(parse_owner(argv[1], &uid, NULL)); 365 | t(stat(argv[2], &attr)); 366 | t(bus_chown(argv[2], uid, attr.st_gid)); 367 | } 368 | 369 | /* Change group. */ 370 | } else if ((argc == 3) && !strcmp(argv[0], "chgrp")) { 371 | t(parse_owner(argv[1], NULL, &gid)); 372 | t(stat(argv[2], &attr)); 373 | t(bus_chown(argv[2], attr.st_uid, gid)); 374 | 375 | } else 376 | return 2; 377 | 378 | return 0; 379 | 380 | fail: 381 | if (!errno) 382 | return 2; 383 | perror(argv0); 384 | return 1; 385 | } 386 | -------------------------------------------------------------------------------- /bus.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #ifndef BUS_H 3 | #define BUS_H 4 | 5 | 6 | #ifndef _DEFAULT_SOURCE 7 | # define _DEFAULT_SOURCE 8 | #endif 9 | #include 10 | #include 11 | 12 | 13 | 14 | #if defined(__GNUC__) 15 | # define BUS_COMPILER_GCC(X) X 16 | #else 17 | # define BUS_COMPILER_GCC(X) /* ignore */ 18 | #endif 19 | 20 | 21 | 22 | /** 23 | * Open the bus for reading only 24 | */ 25 | #define BUS_RDONLY 1 26 | 27 | /** 28 | * Open the bus for writing only 29 | */ 30 | #define BUS_WRONLY 0 31 | 32 | /** 33 | * Open the bus for both reading and writing only 34 | */ 35 | #define BUS_RDWR 0 36 | 37 | /** 38 | * Fail to create bus if its file already exists 39 | */ 40 | #define BUS_EXCL 2 41 | 42 | /** 43 | * Fail if interrupted 44 | */ 45 | #define BUS_INTR 4 46 | 47 | /** 48 | * Function shall fail with errno set to `EAGAIN` 49 | * if the it would block and this flag is used 50 | */ 51 | #define BUS_NOWAIT 1 52 | 53 | 54 | 55 | /** 56 | * The number of bytes in storeable in the shared memory, 57 | * note that this includes the NUL-termination. 58 | * This means that message can be at most one byte smaller. 59 | */ 60 | #define BUS_MEMORY_SIZE 2048 61 | 62 | 63 | 64 | /** 65 | * Bus information 66 | */ 67 | typedef struct bus 68 | { 69 | /** 70 | * The key for the semaphore array 71 | */ 72 | key_t key_sem; 73 | 74 | /** 75 | * The key for the shared memory 76 | */ 77 | key_t key_shm; 78 | 79 | /** 80 | * The ID of the semaphore array 81 | */ 82 | int sem_id; 83 | 84 | /** 85 | * The address of the shared memory 86 | */ 87 | char *message; 88 | 89 | /** 90 | * Non-zero if and only if `bus_poll` has not been 91 | * called since the last `bus_poll_start`, or 92 | * if `bus_poll` failed during reading 93 | */ 94 | int first_poll; 95 | 96 | } bus_t; 97 | 98 | 99 | 100 | /** 101 | * Create a new bus 102 | * 103 | * @param file The pathname of the bus, `NULL` to create a random one 104 | * @param flags `BUS_EXCL` (if `file` is not `NULL`) to fail if the file 105 | * already exists, otherwise if the file exists, nothing 106 | * will happen; 107 | * `BUS_INTR` to fail if interrupted 108 | * @param out_file Output parameter for the pathname of the bus 109 | * @return 0 on success, -1 on error 110 | */ 111 | BUS_COMPILER_GCC(__attribute__((__warn_unused_result__))) 112 | int bus_create(const char *restrict, int, char **restrict); 113 | 114 | /** 115 | * Remove a bus 116 | * 117 | * @param file The pathname of the bus 118 | * @return 0 on success, -1 on error 119 | */ 120 | BUS_COMPILER_GCC(__attribute__((__nonnull__))) 121 | int bus_unlink(const char *); 122 | 123 | 124 | /** 125 | * Open an existing bus 126 | * 127 | * @param bus Bus information to fill 128 | * @param file The filename of the bus 129 | * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR`, 130 | * the value must not be negative 131 | * @return 0 on success, -1 on error 132 | */ 133 | BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) 134 | int bus_open(bus_t *restrict, const char *restrict, int); 135 | 136 | /** 137 | * Close a bus 138 | * 139 | * @param bus Bus information 140 | * @return 0 on success, -1 on error 141 | */ 142 | BUS_COMPILER_GCC(__attribute__((__nonnull__))) 143 | int bus_close(bus_t *); 144 | 145 | 146 | /** 147 | * Broadcast a message on a bus 148 | * 149 | * @param bus Bus information 150 | * @param message The message to write, may not be longer than 151 | * `BUS_MEMORY_SIZE` including the NUL-termination 152 | * @param flags `BUS_NOWAIT` if this function shall fail if 153 | * another process is currently running this 154 | * procedure 155 | * @return 0 on success, -1 on error 156 | */ 157 | BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) 158 | int bus_write(const bus_t *, const char *, int); 159 | 160 | /** 161 | * Broadcast a message on a bus 162 | * 163 | * @param bus Bus information 164 | * @param message The message to write, may not be longer than 165 | * `BUS_MEMORY_SIZE` including the NUL-termination 166 | * @param timeout The time the operation shall fail with errno set 167 | * to `EAGAIN` if not completed 168 | * @param clockid The ID of the clock the `timeout` is measured with, 169 | * it most be a predictable clock 170 | * @return 0 on success, -1 on error 171 | */ 172 | BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))) 173 | int bus_write_timed(const bus_t *, const char *, const struct timespec *, clockid_t); 174 | 175 | 176 | /** 177 | * Listen (in a loop, forever) for new message on a bus 178 | * 179 | * @param bus Bus information 180 | * @param callback Function to call when a message is received, the 181 | * (message, user_data) input parameters will be the read message and 182 | * `user_data` from `bus_read`'s parameter with the 183 | * same name. The message must have been parsed or 184 | * copied when `callback` returns as it may be over 185 | * overridden after that time. `callback` should 186 | * return either of the the values: 187 | * * 0: stop listening 188 | * * 1: continue listening 189 | * * -1: an error has occurred 190 | * However, the function [`bus_read`] will invoke 191 | * `callback` with `message` set to `NULL`one time 192 | * directly after it has started listening on the 193 | * bus. This is to the the program now it can safely 194 | * continue with any action that requires that the 195 | * programs is listening on the bus. 196 | * @param user_data Parameter passed to `callback` 197 | * @return 0 on success, -1 on error 198 | */ 199 | BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))) 200 | int bus_read(const bus_t *restrict, int (*)(const char *, void *), void *); 201 | 202 | /** 203 | * Listen (in a loop, forever) for new message on a bus 204 | * 205 | * @param bus Bus information 206 | * @param callback Function to call when a message is received, the 207 | * (message, user_data) input parameters will be the read message and 208 | * `user_data` from `bus_read`'s parameter with the 209 | * same name. The message must have been parsed or 210 | * copied when `callback` returns as it may be over 211 | * overridden after that time. `callback` should 212 | * return either of the the values: 213 | * * 0: stop listening 214 | * * 1: continue listening 215 | * * -1: an error has occurred 216 | * However, the function [`bus_read`] will invoke 217 | * `callback` with `message` set to `NULL`one time 218 | * directly after it has started listening on the 219 | * bus. This is to the the program now it can safely 220 | * continue with any action that requires that the 221 | * programs is listening on the bus. 222 | * @param user_data Parameter passed to `callback` 223 | * @param timeout The time the operation shall fail with errno set 224 | * to `EAGAIN` if not completed, note that the callback 225 | * function may or may not have been called 226 | * @param clockid The ID of the clock the `timeout` is measured with, 227 | * it most be a predictable clock 228 | * @return 0 on success, -1 on error 229 | */ 230 | BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))) 231 | int bus_read_timed(const bus_t *restrict, int (*)(const char *, void *), 232 | void *, const struct timespec *, clockid_t); 233 | 234 | 235 | /** 236 | * Announce that the thread is listening on the bus. 237 | * This is required so the will does not miss any 238 | * messages due to race conditions. Additionally, 239 | * not calling this function will cause the bus the 240 | * misbehave, is `bus_poll` is written to expect 241 | * this function to have been called. 242 | * 243 | * @param bus Bus information 244 | * @return 0 on success, -1 on error 245 | */ 246 | BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) 247 | int bus_poll_start(bus_t *); 248 | 249 | /** 250 | * Announce that the thread has stopped listening on the bus. 251 | * This is required so that the thread does not cause others 252 | * to wait indefinitely. 253 | * 254 | * @param bus Bus information 255 | * @return 0 on success, -1 on error 256 | */ 257 | BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) 258 | int bus_poll_stop(const bus_t *); 259 | 260 | /** 261 | * Wait for a message to be broadcasted on the bus. 262 | * The caller should make a copy of the received message, 263 | * without freeing the original copy, and parse it in a 264 | * separate thread. When the new thread has started be 265 | * started, the caller of this function should then 266 | * either call `bus_poll` again or `bus_poll_stop`. 267 | * 268 | * @param bus Bus information 269 | * @param flags `BUS_NOWAIT` if the bus should fail and set `errno` to 270 | * `EAGAIN` if there isn't already a message available on the bus 271 | * @return The received message, `NULL` on error 272 | */ 273 | BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) 274 | const char *bus_poll(bus_t *, int); 275 | 276 | /** 277 | * Wait for a message to be broadcasted on the bus. 278 | * The caller should make a copy of the received message, 279 | * without freeing the original copy, and parse it in a 280 | * separate thread. When the new thread has started be 281 | * started, the caller of this function should then 282 | * either call `bus_poll_timed` again or `bus_poll_stop`. 283 | * 284 | * @param bus Bus information 285 | * @param timeout The time the operation shall fail with errno set 286 | * to `EAGAIN` if not completed 287 | * @param clockid The ID of the clock the `timeout` is measured with, 288 | * it most be a predictable clock 289 | * @return The received message, `NULL` on error 290 | */ 291 | BUS_COMPILER_GCC(__attribute__((__nonnull__(1), __warn_unused_result__))) 292 | const char *bus_poll_timed(bus_t *, const struct timespec *, clockid_t); 293 | 294 | 295 | /** 296 | * Change the ownership of a bus 297 | * 298 | * `stat(2)` can be used of the bus's associated file to get the bus's ownership 299 | * 300 | * @param file The pathname of the bus 301 | * @param owner The user ID of the bus's new owner 302 | * @param group The group ID of the bus's new group 303 | * @return 0 on success, -1 on error 304 | */ 305 | BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) 306 | int bus_chown(const char *, uid_t, gid_t); 307 | 308 | /** 309 | * Change the permissions for a bus 310 | * 311 | * `stat(2)` can be used of the bus's associated file to get the bus's permissions 312 | * 313 | * @param file The pathname of the bus 314 | * @param mode The permissions of the bus, any permission for a user implies 315 | * full permissions for that user, except only the owner may 316 | * edit the bus's associated file 317 | * @return 0 on success, -1 on error 318 | */ 319 | BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))) 320 | int bus_chmod(const char *, mode_t); 321 | 322 | 323 | 324 | #endif 325 | 326 | -------------------------------------------------------------------------------- /bus.texinfo: -------------------------------------------------------------------------------- 1 | \input texinfo @c -*-texinfo-*- 2 | 3 | @c %**start of header 4 | @setfilename bus.info 5 | @settitle bus 6 | @afourpaper 7 | @documentencoding UTF-8 8 | @documentlanguage en 9 | @finalout 10 | @c %**end of header 11 | 12 | 13 | @dircategory Interprorcess Communication 14 | @direntry 15 | * bus: (bus). A simple daemonless system for broadcasting messages locally 16 | @end direntry 17 | 18 | 19 | @copying 20 | Copyright @copyright{} 2015 Mattias Andrée 21 | 22 | @quotation 23 | Permission is granted to copy, distribute and/or modify this document 24 | under the terms of the GNU Free Documentation License, Version 1.3 or 25 | any later version published by the Free Software Foundation; with no 26 | Invariant Sections, with no Front-Cover Texts, and with no Back-Cover 27 | Texts. A copy of the license is included in the section entitled 28 | ``GNU Free Documentation License''. 29 | @end quotation 30 | @end copying 31 | 32 | @ifnottex 33 | @node Top 34 | @top bus -- A simple daemonless system for broadcasting messages locally 35 | @insertcopying 36 | @end ifnottex 37 | 38 | @titlepage 39 | @title bus 40 | @subtitle A simple daemonless system for broadcasting messages locally 41 | @author by Mattias Andrée (maandree) 42 | 43 | @page 44 | @vskip 0pt plus 1filll 45 | @insertcopying 46 | @page 47 | @end titlepage 48 | 49 | @contents 50 | 51 | 52 | @iftex 53 | @macro xrm{} 54 | @rm{} 55 | @end macro 56 | @macro xtt{} 57 | @tt{} 58 | @end macro 59 | @end iftex 60 | 61 | @ifnottex 62 | @macro xrm{} 63 | @end macro 64 | @macro xtt{} 65 | @end macro 66 | @end ifnottex 67 | 68 | @menu 69 | * Overview:: Brief overview of @command{bus}. 70 | * Standard:: How to use @command{bus} properly. 71 | * Invoking:: Executing @command{bus}. 72 | * Interface:: Using @command{libbus}. 73 | * Protocol:: How communication over @command{bus} works internally. 74 | * Rationale:: Why @command{bus}? 75 | * Examples:: Usecase examples and API-demonstration. 76 | * GNU Free Documentation License:: Copying and sharing this manual. 77 | 78 | @detailmenu 79 | --- The Detailed Node Listing --- 80 | 81 | Invoking 82 | 83 | * bus create:: Create a bus. 84 | * bus remove:: Remove a bus. 85 | * bus listen:: Listen for new message on a bus. 86 | * bus wait:: Listen for one new message only on a bus. 87 | * bus broadcast:: Broadcast a message on a bus. 88 | * bus chmod:: Change permissions on a bus. 89 | * bus chown:: Change ownership of a bus. 90 | * bus chgrp:: Change group ownership of a bus. 91 | 92 | Examples 93 | 94 | * Audio-volume control:: 95 | * Telephony and music:: 96 | * Timed:: 97 | * Nonblocking:: 98 | * Daemon-dependencies:: 99 | 100 | @end detailmenu 101 | @end menu 102 | 103 | 104 | 105 | @node Overview 106 | @chapter Overview 107 | 108 | @command{bus} is a stupid-simple, thrilless, daemonless interprocess 109 | communication system for broadcasting messages. It is a lightweight 110 | alternative to a two-phase interprocess flexible barrier. 111 | 112 | @command{bus} uses a System V semaphore array and System V shared 113 | memory. Buses are named; the key of the semaphore array and the 114 | shared memory is stored in a regular file. 115 | 116 | The shared memory used by @command{bus} is always 2048 bytes. 117 | Additionally all messages should be encoded in UTF-8 and not contain 118 | any NULL characters, except they @emph{must} always end with a NULL 119 | byte. Furthermore messages should be prefixed with the process 120 | identifer of the process whence the message originated, followed 121 | by a space. If the process is ephemeral@footnote{The process exits 122 | after the broadcast, or shortly thereafter.}, 0 should be used 123 | instead of the process identifier. 124 | 125 | Communication over @command{bus} is synchronous. The broadcast call 126 | does not return until all listeners have received (and copied) the 127 | message. A malfunctioning program can lock the bus. 128 | 129 | This software package contains a C library and a command line 130 | utility. The package python-bus provides a Python 3 module. 131 | 132 | 133 | 134 | @node Standard 135 | @chapter Standard 136 | 137 | The command @command{bus create} can be used to create new buses. By 138 | convention, buses should be stored in @file{$XDG_RUNTIME_DIR/bus}, 139 | this is what @command{bus create} does if no pathname is given. The 140 | pathname of the bus should be tracked using @env{BUS_X}, where @env{X} 141 | is replaced with either: 142 | 143 | @table @env 144 | @item GENERIC 145 | For the bus used in generic cases. That is all but the cases 146 | of the buses listed below. 147 | @item AUDIO 148 | For the bus used in with the audio subsystem is involved. 149 | @item VIDEO 150 | For the bus used in with the video subsystem is involved. 151 | @item INPUT 152 | For the bus used in with the input subsystem is involved. 153 | @item FILES 154 | For the bus used in with the storage subsystem is involved. 155 | @end table 156 | 157 | This list may be extended in the future. Therefore, and for 158 | other conventions, project-private buses should be tracked 159 | using @env{X_BUS}, where @env{X} is the project name. 160 | 161 | Messages broadcasted on a bus cannot be longer than 2047 bytes, 162 | excluding NUL termination. Message should be encoded in UTF-8, 163 | and most not contain the NUL character. 164 | 165 | Broadcasted message should start with the process ID whence 166 | the message originated, followed by a single regular space. 167 | If the process is ephemeral@footnote{The process exits after 168 | the broadcast, or shortly thereafter.}, 0 should be used instead 169 | of the process identifier. 170 | 171 | 172 | 173 | @node Invoking 174 | @chapter Invoking 175 | 176 | @command{bus} includes the following commands: 177 | 178 | @table @command 179 | @item create 180 | Create a bus. 181 | See @ref{bus create} for more information. 182 | @item remove 183 | Remove a bus. 184 | See @ref{bus remove} for more information. 185 | @item listen 186 | Listen for new message on a bus. 187 | See @ref{bus listen} for more information. 188 | @item wait 189 | Listen for one new message only on a bus. 190 | See @ref{bus wait} for more information. 191 | @item broadcast 192 | Broadcast a message on a bus. 193 | See @ref{bus broadcast} for more information. 194 | @item chmod 195 | Change permissions on a bus. 196 | See @ref{bus chmod} for more information. 197 | @item chown 198 | Change ownership of a bus. 199 | See @ref{bus chown} for more information. 200 | @item chgrp 201 | Change group ownership of a bus. 202 | See @ref{bus chgrp} for more information. 203 | @end table 204 | 205 | Upon successful completion, these commands exit with the value 206 | 0. On failure, they exit with the value 1. If the command is 207 | not recognised the exit value is 2. 208 | 209 | @menu 210 | * bus create:: Create a bus. 211 | * bus remove:: Remove a bus. 212 | * bus listen:: Listen for new message on a bus. 213 | * bus wait:: Listen for one new message only on a bus. 214 | * bus broadcast:: Broadcast a message on a bus. 215 | * bus chmod:: Change permissions on a bus. 216 | * bus chown:: Change ownership of a bus. 217 | * bus chgrp:: Change group ownership of a bus. 218 | @end menu 219 | 220 | 221 | 222 | @node bus create 223 | @section @command{bus create} 224 | 225 | The syntax for invocation of @command{bus create} is 226 | @example 227 | bus create [-x] [--] [@var{PATHNAME}] 228 | @end example 229 | 230 | The command creates a bus and stores the key to it in the 231 | file @var{PATHNAME}. If @var{PATHNAME} is omitted, a 232 | random pathname in @file{$XDG_RUNTIME_DIR/bus} will be 233 | used and printed to stdout. 234 | 235 | If @option{-x} is used, the command will fail if 236 | the file @var{PATHNAME} already exists. 237 | 238 | 239 | 240 | 241 | @node bus remove 242 | @section @command{bus remove} 243 | 244 | The syntax for invocation of @command{bus remove} is 245 | @example 246 | bus remove [--] @var{PATHNAME} 247 | @end example 248 | 249 | The command removes the bus whose key is stored in 250 | the file @var{PATHNAME}. The file holding the 251 | key is also unlinked. 252 | 253 | 254 | 255 | @node bus listen 256 | @section @command{bus listen} 257 | 258 | The syntax for invocation of @command{bus command} is 259 | @example 260 | bus listen [--] @var{PATHNAME} @var{COMMAND} 261 | @end example 262 | 263 | The command listens for new messages on the bus whose 264 | key is stored in the file @var{PATHNAME}. Once a message 265 | is received, @var{COMMAND} will be spawned with the 266 | environment variable @env{msg} (lowercased) set to the 267 | received message. @sc{POSIX} shell syntax applies to 268 | @var{COMMAND}. 269 | 270 | 271 | @node bus wait 272 | @section @command{bus wait} 273 | 274 | The syntax for invocation of @command{bus wait} is 275 | @example 276 | bus wait [--] @var{PATHNAME} @var{COMMAND} 277 | @end example 278 | 279 | The command listens for a new message on the bus whose 280 | key is stored in the file @var{PATHNAME}. Once a message 281 | is received, the process will stop listening for more 282 | messages and @var{COMMAND} will be spawned with the 283 | environment variable @env{msg} (lowercased) set to the 284 | received message. @sc{POSIX} shell syntax applies to 285 | @var{COMMAND}. 286 | 287 | 288 | 289 | @node bus broadcast 290 | @section @command{bus broadcast} 291 | 292 | The syntax for invocation of @command{bus broadcast} is 293 | @example 294 | bus broadcast [-n] [--] @var{PATHNAME} @var{MESSAGE} 295 | @end example 296 | 297 | The command broadcasts the message @var{MESSAGE} on the 298 | bus whose key is stored in the file @var{PATHNAME}. 299 | 300 | 301 | 302 | @node bus chmod 303 | @section @command{bus chmod} 304 | 305 | The syntax for invocation of @command{bus chmod} is 306 | @example 307 | bus chmod [--] @var{PERMISSIONS} @var{PATHNAME} 308 | @end example 309 | 310 | This command changes who have access to the bus whose key 311 | is stored in the file @var{PATHNAME}. In the permissions, 312 | the owner, the group, and others (not in tgroup) are 313 | represented by the symbols @code{u}@footnote{@code{u} 314 | stands for `user'.}, @code{g}, and @code{o}, respectively. 315 | The permissions string is imagined to have always be 316 | prefixed with an @code{=}. This symbols means that all user 317 | classes list after it, and only those classes, as permission 318 | to use the bus. Similarly the symbols @code{+} and @code{-} 319 | can be used to grant and revoke access, respectively. The 320 | symbols @code{=}, @code{+}, and @code{-} can be mixed, and 321 | are interpreted from left to right. Alternatively the 322 | permissions string can be a octal number, where the owner 323 | is represented by any bit in 700 (100, 200, or 400, or any 324 | combination thereof), the group is represented by any bit 325 | in 70, and others (not in the group) is represented by any 326 | bit in 7. 327 | 328 | The current permission of the bus can be retrieved by 329 | running @command{stat} over the file @var{PATHNAME}. 330 | 331 | 332 | 333 | @node bus chown 334 | @section @command{bus chown} 335 | 336 | The syntax for invocation of @command{bus chown} is 337 | @example 338 | bus chown [--] @var{OWNER}[:@var{GROUP}] @var{PATHNAME} 339 | @end example 340 | 341 | This command changes the owner, that owns the bus whose 342 | key is stored in the file @var{PATHNAME}, to the specified 343 | owner. The owner can be specified either with a numerical 344 | user identifier or with a user name. If a group is 345 | specified, the bus's owner-group will be set to that group, 346 | otherwise the group will remain unchanged (not changed 347 | to the group of the new owner.) The group can be specified 348 | either with a numerical group identifier or with a group 349 | name. 350 | 351 | The current ownership of the bus can be retrieved by 352 | running @command{stat} over the file @var{PATHNAME}. 353 | 354 | 355 | 356 | @node bus chgrp 357 | @section @command{bus chgrp} 358 | 359 | The syntax for invocation of @command{bus chgrp} is 360 | @example 361 | bus chgrp [--] @var{GROUP} @var{PATHNAME} 362 | @end example 363 | 364 | This command changes the group, that owns the bus whose 365 | key is stored in the file @var{PATHNAME}, to the specified 366 | group. The group can be specified either with a numerical 367 | group identifier or with a group name. 368 | 369 | The current ownership of the bus can be retrieved by 370 | running @command{stat} over the file @var{PATHNAME}. 371 | 372 | 373 | 374 | @node Interface 375 | @chapter Interface 376 | 377 | To use @command{libbus} in your C program, include the 378 | header file @file{} and link with the flag 379 | @option{-lbus}. 380 | 381 | With exception to @code{bus_poll} and @code{bus_poll_timed}, 382 | all functions return @code{0} upon successful completion, 383 | and @code{-1} in case of failure. @code{bus_poll} and 384 | @code{bus_poll_timed} return @code{NULL} on failure. 385 | On failure on all functions set @code{errno} to indicate 386 | what went wrong. 387 | 388 | @file{} defines the following functions: 389 | 390 | @table @code 391 | @item int bus_create(const char *file, int flags, char **out_file) 392 | This function creates a bus with the asscoiated pathname 393 | specifed by the value of the parameter @code{file}. If 394 | @code{file} is @code{NULL} a random pathname is selected. 395 | This pathname adheres to the convention set forth by 396 | in @ref{Standard}. 397 | 398 | If @code{file} is not @code{NULL} the function fails if the 399 | file already exists if @code{flags} contains @code{BUS_EXCL}. 400 | Otherwise if @code{file} is not @code{NULL}, the function 401 | does nothing if the file already exists. 402 | 403 | If @code{flags} contains @code{BUS_INTR}, the function fails 404 | if it is interrupted. 405 | 406 | Unless @code{out_file} is NULL, the pathname of the bus 407 | should be stored in a new char array stored in @code{*out_file}. 408 | The caller must free the allocated stored in @code{*out_file}. 409 | 410 | If the processes cannot allocate enough memory to perform 411 | the action, the function sets @code{errno} to @code{ENOMEM} 412 | and fails. It may also fail and set @code{errno} to any of 413 | the errors specified for the system calls @code{open} and 414 | @code{write}. 415 | 416 | @item int bus_unlink(const char *file) 417 | This function removes the bus assoicated with the pathname 418 | stored in the parameter @code{file}. The function also 419 | unlinks the file. 420 | 421 | The function may set @code{errno} to any of the following 422 | values and fail for the specified reasons: 423 | @table @code 424 | @item EINVAL 425 | The bus does not exist. 426 | @item EACCES 427 | Operation permission is denied to the calling process. 428 | @item EPERM 429 | The user does not have permission to remove the bus. 430 | @end table 431 | @noindent 432 | It may also fail and set @code{errno} to any of the errors 433 | specified for the system calls @code{unlink} and @code{open}, 434 | and the functions @code{semget} and @code{shmget}. 435 | 436 | @item int bus_open(bus_t *bus, const char *file, int flags) 437 | This function acquires resources required for the process 438 | to use the bus associated with the filename stored in the 439 | parameter @code{file}. The function also stores the resources 440 | in @code{bus} for use by other @command{bus} functions. 441 | 442 | Values for @code{flags} are constructed by a bitwise 443 | inclusive @sc{or} of flags from the following list. 444 | @table @code 445 | @item BUS_RDONLY 446 | The process will only be using the bus for receiving messages. 447 | @item BUS_WRONLY 448 | The process will only be using the bus for sending messages. 449 | @item BUS_RDWR 450 | The process will use the bus for both receiving and sending 451 | messages. 452 | @end table 453 | 454 | The function may set @code{errno} to any of the following 455 | values and fail for the specified reasons: 456 | @table @code 457 | @item ENOMEM 458 | The process cannot allocate enough memory to perform the 459 | action. 460 | @item EACCES 461 | Operation permission is denied to the calling process. 462 | @item EINVAL 463 | The described bus does not exist. 464 | @end table 465 | @noindent 466 | It may also fail and set @code{errno} to any of the errors 467 | specified for the system call @code{open}. 468 | 469 | @item int bus_close(bus_t *bus) 470 | This function disposes of resources allocated to the 471 | process, as referenced in the parameter @code{bus}. 472 | 473 | The function fails and sets @code{errno} to @code{EINVAL} 474 | if the bus does not exist. 475 | 476 | @item int bus_write(const bus_t *bus, const char *message, int flags) 477 | This function broadcasts a message on the bus whose 478 | information is stored in the parameter @code{bus}. The 479 | message read by the function is stored in the parameter 480 | @code{message}. It may not exceeed 2048 bytes, including 481 | NUL termination. 482 | 483 | The function shall fail, and set @code{errno} to 484 | @code{EAGAIN}, if the call would suspend the process and 485 | @code{flags} contains @code{BUS_NOWAIT}. 486 | 487 | The function may fail and set @code{errno} to any of the 488 | errors specified for the function @code{semop}. 489 | 490 | @item int bus_write_timed(const bus_t *bus, const char *message, const struct timespec *timeout, clockid_t clockid) 491 | This function behaves like @code{bus_write}, except if it 492 | is not able to write the message within the specified time, 493 | it will fail and set @code{errno} to @code{EAGAIN}. The 494 | time is specified as an absolute time using the parameter 495 | @code{timeout}. The behaviour is unspecified if @code{timeout} 496 | is @code{NULL}. @code{timeout} is measured with the clock whose 497 | identifier is specified by the parameter @code{clockid}. This 498 | clock must be a predicitable clock@footnote{There are probably 499 | other, undocumented, seemingly arbitrary restrictions too.}. 500 | 501 | The function may fail and set @code{errno} to any of the 502 | errors specified for the functions @code{semop} and 503 | @code{clock_gettime}. 504 | 505 | @item int bus_read(const bus_t *bus, int (*callback)(const char *message, void *user_data), void *user_data) 506 | This function waits for new message to be sent on the bus 507 | specified in the @code{bus} parameter, as provieded by a 508 | previous call to the function @code{bus_open}. Once a 509 | message is received, the parameter-function @code{callback} 510 | is invoked. The parameter @code{message} in @code{callback} 511 | is the received message, and @code{user_data} in 512 | @code{callback} should be @code{user_data} from @code{bus_read}. 513 | However, once the function [@code{bus_read}] has ensured 514 | that it will receive any message sent on the bus, it shall 515 | invoke the parameter-function @code{callback} with 516 | @code{message} set to @code{NULL}, to notify the process that 517 | it can perform any action that requires that it is listening 518 | on the bus. 519 | 520 | After @code{callback} returns, @code{message} may be override. 521 | Therefore @code{callback} should copy message and start a new 522 | thread that uses the copy of @code{message}. @code{callback} 523 | shall return @code{-1} on failure, @code{0} if the function 524 | [@code{bus_read}] should stop listening, or @code{1} if 525 | the function should continue listening. 526 | 527 | The function may fail and set @code{errno} to any of the 528 | errors specified for the function @code{semop}. 529 | 530 | @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) 531 | This function behaves like @code{bus_read}, except it will 532 | automatically fail and set @code{errno} to @code{EAGAIN} when 533 | the specified time has passed. The time is specified as an 534 | absolute time using the parameter @code{timeout}. The 535 | behaviour is unspecified if @code{timeout} is @code{NULL}. 536 | @code{timeout} is measured with the clock whose identifier 537 | is specified by the parameter @code{clockid}. This clock 538 | must be a predicitable clock@footnote{There are probably 539 | other, undocumented, seemingly arbitrary restrictions too.}. 540 | 541 | The function may fail and set @code{errno} to any of the 542 | errors specified for the functions @code{semop} and 543 | @code{clock_gettime}. 544 | 545 | @item int bus_poll_start(bus_t *bus) 546 | @itemx int bus_poll_stop(const bus_t *bus) 547 | @itemx const char *bus_poll(bus_t *bus, int flags) 548 | @itemx const char *bus_poll_timed(bus_t *bus, const struct timespec *timeout, clockid_t clockid) 549 | The function @code{bus_poll} waits for a message to be 550 | broadcasted on the bus, and return the message it receives. 551 | The function fails if @code{flags} contains @code{BUS_NOWAIT} 552 | and there is not already a message waiting on the bus. 553 | Received messages shall be copied and parsed, and acted 554 | upon, in a separate thread, and the function @code{bus_poll} 555 | or the function @code{bus_poll_stop} called again as soon 556 | as possible. 557 | 558 | The funcion @code{bus_poll_start} must be called before 559 | @code{bus_poll} is called for the first time. When the 560 | process is done listening on the bus, it must call the 561 | function @code{bus_poll_stop}. 562 | 563 | The function @code{bus_poll_timed} behaves like the function 564 | @code{bus_poll}, except if it is not able to read a message 565 | within the specified time, it will fail and set @code{errno} 566 | to @code{EAGAIN}. The time is specified as an absolute time 567 | using the parameter @code{timeout}. The behaviour is 568 | unspecified if @code{timeout} is @code{NULL}. @code{timeout} 569 | is measured with the clock whose identifier is specified by 570 | the parameter @code{clockid}. This clock must be a predicitable 571 | clock@footnote{There are probably other, undocumented, 572 | seemingly arbitrary restrictions too.}. 573 | 574 | Upon successful completion, the functions @code{bus_poll} and 575 | @code{bus_poll_timed} returns the received message. 576 | 577 | These functions may fail and set @code{errno} to any of the 578 | errors specified for the function @code{semop}. The function 579 | @code{bus_poll_timed} may also set @code{errno} to any of 580 | the errors specified for @code{clock_gettime}. 581 | 582 | @item int bus_chown(const char *file, uid_t owner, gid_t group) 583 | This function changes the owner and the group of the bus, 584 | associated with the file whose pathname is stored in the 585 | parameter @code{file}, to the owner and group specified by 586 | the parameters @code{owner} and @code{group}, respectively. 587 | 588 | The current ownership of a bus can be retrieved by calling 589 | @code{stat} over the pathname of the bus. 590 | 591 | The function may fail and set @code{errno} to any of the 592 | errors specified for the functions @code{bus_open}, 593 | @code{chown}, @code{semget}, @code{shmget}, and @code{shmctl} 594 | as well as any errors specified for the commands 595 | @code{IPC_STAT} and @code{IPC_SET} for the function 596 | @code{semctl}. 597 | 598 | @item int bus_chmod(const char *file, mode_t mode) 599 | This function gives access to the bus associated with the 600 | file whose pathname is stored in the parameter @code{file} 601 | according to the following rules: 602 | 603 | @itemize @bullet{} 604 | @item 605 | If @code{mode} contains any of the bits @code{S_IRWXU} 606 | contains, the owner should be given full access to the 607 | bus. Otherwise the owner should have no access. 608 | @item 609 | If @code{mode} contains any of the bits @code{S_IRWXG} 610 | contains, the group should be given read and write 611 | access to the bus. Otherwise the group should have no 612 | access. 613 | @item 614 | If @code{mode} contains any of the bits @code{S_IRWXO} 615 | contains, users that are neither the owner nor member 616 | of the group should be given read and write access to 617 | the bus. Otherwise they should have no access. 618 | @end itemize 619 | 620 | The current permissions of a bus can be retrieved by calling 621 | @code{stat} over the pathname of the bus. 622 | 623 | The function may fail and set @code{errno} to any of the 624 | errors specified for the functions @code{bus_open}, 625 | @code{chmode}, @code{semget}, @code{shmget}, and @code{shmctl} 626 | as well as any errors specified for the commands 627 | @code{IPC_STAT} and @code{IPC_SET} for the function 628 | @code{semctl}. 629 | @end table 630 | 631 | There is not reason for poking around in @code{bus_t} 632 | (@code{struct bus}). It should be considered opaque. 633 | You can read the documentation in @file{} if 634 | you want to know what is in it. 635 | 636 | 637 | 638 | 639 | @node Protocol 640 | @chapter Protocol 641 | 642 | @command{bus} is built upon following three procedures. 643 | 644 | @noindent 645 | @code{create} 646 | @example 647 | @w{@xrm{}Select a filename.@xtt{}} 648 | 649 | @w{@xrm{}Create XSI semaphore array @{@code{S} = 0, @code{W} = 0, @code{X} = 1, @code{Q} = 0, @code{N} = 0@}@xtt{}} 650 | @w{@xrm{}with random key. Store the semaphore array's key in decimal form@xtt{}} 651 | @w{@xrm{}on the first line in the selected file.@xtt{}} 652 | 653 | @w{@xrm{}Create XSI shared memory, with an allocation of 2048 bytes, with@xtt{}} 654 | @w{@xrm{}a random key. Store the shared memory's key in decimal form on@xtt{}} 655 | @w{@xrm{}the second line in the selected file.@xtt{}} 656 | @end example 657 | 658 | @noindent 659 | @code{broadcast} 660 | @example 661 | with P(X): 662 | Z(W) 663 | @w{@xrm{}Write NUL-terminate message to shared memory@xtt{}} 664 | with V(N): -- (1) 665 | Q := 0 666 | Z(S) 667 | 668 | -- (1) @w{@xrm{}may be omitted if semaphores are known that@xtt{}} 669 | @w{P()@xrm{}, @xtt{}Z()@xrm{}, @xtt{}V()@xrm{} cannot create a race condition@xtt{}} 670 | @w{@xrm{}with a processes running @xtt{}Z()@xrm{}.@xtt{}} 671 | @end example 672 | 673 | @noindent 674 | @code{listen} 675 | @example 676 | with V(S): 677 | forever: 678 | V(Q) 679 | Z(Q) 680 | @w{@xrm{}Read NUL-terminated message from shared memory@xtt{}} 681 | if breaking: 682 | break 683 | with V(W): 684 | with P(S): 685 | Z(S) 686 | Z(N) 687 | @end example 688 | 689 | @noindent 690 | @code{V(a)} means that semaphore a is released.@* 691 | @code{P(a)} means that semaphore a is acquired.@* 692 | @code{Z(a)} means that the process waits for semaphore a to become 0.@* 693 | @code{with P(a)} that @code{P(a)} is done before the entering the scope, 694 | and @code{V(a)} is done when exiting the scope. It also means that 695 | these actions [@code{P(a)} and @code{V(a)}] are undone when the process 696 | exits, or if the call fails.@* 697 | @code{with V(a)} is to @code{V(a)} as @code{with P(a)} is to @code{P(a)}. 698 | 699 | 700 | 701 | @node Rationale 702 | @chapter Rationale 703 | 704 | We need an interprocess communication system similar 705 | to message queues. But we need broadcasting rather than 706 | anycasting, so we have a fast, simple and daemonless 707 | system for announcing events to any processes that 708 | might be interested. 709 | 710 | 711 | 712 | @node Examples 713 | @chapter Examples 714 | 715 | This chapter contains usecase examples and 716 | API-demonstrations. You will find that they are (on 717 | a standard installation) installed on your system. 718 | 719 | @menu 720 | * Audio-volume control:: 721 | * Telephony and music:: 722 | * Timed:: 723 | * Nonblocking:: 724 | * Daemon-dependencies:: 725 | @end menu 726 | 727 | 728 | 729 | 730 | @node Audio-volume control 731 | @section Audio-volume control 732 | 733 | Assume you have program that display the audio volume. 734 | This program checks every second third if the volume 735 | have changed. 736 | 737 | Also assume that you use @command{amixer} to change the 738 | volume, most often by using keybindings via @command{xbindkeys}. 739 | 740 | To reduce the delay, you want to send a signal to the 741 | monitor program that the volume have changed. For this 742 | more primitive IPC is sufficient, but lets assume there 743 | are other programs interested in this information too. 744 | 745 | To accomplish this, you create a wrapper for @command{amixer} 746 | that broadcasts updates on a bus. This wrapper is 747 | installed as @command{~/.local/bin/amixer}, and 748 | @command{~/.local/bin/} is included in @env{$PATH} 749 | before @command{/usr/bin}. 750 | @* 751 | 752 | @noindent 753 | Before starting run @command{./init}, this code is 754 | should be run from your profile file if you want to 755 | implement this on your system. After running 756 | @command{./init}, you can start one or more listeners 757 | by running @command{./alsa-monitor}. 758 | 759 | To change the volume run 760 | @code{./amixer -c 0 -- set Master 5%+} or similar. 761 | 762 | When you are done run @command{./cleanup}. 763 | 764 | 765 | @subsubheading @file{./amixer} 766 | @example 767 | #!/bin/sh 768 | /usr/bin/amixer "$@@" 769 | for arg in "$@@"; do 770 | if [ "$@{arg@}" = "set" ] || \ 771 | [ "$@{arg@}" = "sset" ] || \ 772 | [ "$@{arg@}" = "cset" ]; then 773 | exec bus broadcast "/tmp/example-bus" '0 volume-changed *' 774 | fi 775 | done 776 | @end example 777 | 778 | 779 | @subsubheading @file{./cleanup} 780 | @example 781 | #!/bin/sh 782 | exec bus remove "/tmp/example-bus" 783 | @end example 784 | 785 | 786 | @subsubheading @file{./init} 787 | @example 788 | #!/bin/sh 789 | bus create "/tmp/example-bus" 790 | 791 | # @w{@xrm{}The following code is more suitable in the real world,@xtt{}} 792 | # @w{@xrm{}if used, the other files should use @xtt{}"$@{BUS_AUDIO@}"} 793 | # @w{@xrm{}instead of @xtt{}"/tmp/example-bus"@xrm{}.@xtt{}} 794 | # 795 | # export BUS_AUDIO="$@{XDG_RUNTIME_DIR@}/bus/audio" 796 | # if [ ! -f "$@{BUS_AUDIO@}" ]; then 797 | # bus create "$@{BUS_AUDIO@}" 798 | # fi 799 | @end example 800 | 801 | 802 | @subsubheading @file{./monitor} 803 | @example 804 | #!/bin/sh 805 | if [ $# = 1 ]; then 806 | if [ "$(echo "$@{1@}" | cut -d ' ' -f 2)" = "volume-changed" ]; then 807 | printf '\e[H\e[2J' 808 | amixer get Master 809 | fi 810 | exit 0 811 | fi 812 | 813 | exec 2>/dev/null 814 | 815 | printf '\e[?1049h\e[H\e[2J' 816 | trap -- "printf '\e[?1049l'" SIGINT 817 | bus listen "/tmp/example-bus" \'"$@{0/\'/\'\\\'\'@}"\'' "$@{msg@}"' 818 | @end example 819 | 820 | 821 | 822 | @node Telephony and music 823 | @section Telephony and music 824 | 825 | Assume you have a music player and a telephony program. 826 | You might like it if the music player pauses whenever 827 | you make or receive a call. You may also like it, if 828 | the music resumed when the call ended. 829 | 830 | In this example we will assume you the have @command{mocp} 831 | (@command{moc} package) running. And we will use the shell to 832 | simulate a telephony program. 833 | @* 834 | 835 | @noindent 836 | First of, run make to build this example. Before 837 | starting run @command{./init}. And when you are 838 | done run @command{./cleanup}. 839 | 840 | In one terminal run @command{./monitor}. This program 841 | will pause @command{mocp} when you make or receive a call, 842 | it will also resume @command{mocp} when all calls have 843 | ended if it did pause @command{mocp}. 844 | 845 | Then start any positive number of terminals. 846 | We will pretend that each of them are telephony 847 | programs. To make or receive a call, run 848 | @command{./receive-or-make-call}, when you want to 849 | end the pretend call, run @command{./end-call} from 850 | the terminal (or more accurately, from the same 851 | process.) 852 | 853 | 854 | @subsubheading @file{./Makefile} 855 | @example 856 | COMMANDS = init cleanup monitor end-call receive-or-make-call 857 | 858 | all: $@{COMMANDS@} 859 | %: %.c 860 | $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< 861 | clean: 862 | -rm $@{COMMANDS@} 863 | 864 | .PHONY: all clean 865 | @end example 866 | 867 | 868 | @subsubheading @file{./cleanup.c} 869 | @example 870 | #include 871 | #include 872 | 873 | int main() 874 | @{ 875 | return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); 876 | @} 877 | @end example 878 | 879 | 880 | @subsubheading @file{./end-call.c} 881 | @example 882 | #include 883 | #include 884 | #include 885 | #include 886 | 887 | #define t(stmt) if (stmt) goto fail 888 | 889 | static char message[BUS_MEMORY_SIZE]; 890 | 891 | int main() 892 | @{ 893 | bus_t bus; 894 | sprintf(message, "%ji unforce-pause", (intmax_t)getppid()); 895 | /* @w{@xrm{}Yes, PPID; in this example we pretend the shell is the telephony process.@xtt{}} */ 896 | t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); 897 | t (bus_write(&bus, message, 0)); 898 | bus_close(&bus); 899 | return 0; 900 | 901 | fail: 902 | perror("end-call"); 903 | bus_close(&bus); 904 | return 1; 905 | @} 906 | @end example 907 | 908 | 909 | @subsubheading @file{./init.c} 910 | @example 911 | #include 912 | #include 913 | 914 | int main() 915 | @{ 916 | return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); 917 | @} 918 | @end example 919 | 920 | 921 | @subsubheading @file{./monitor.c} 922 | @example 923 | #include 924 | #include 925 | #include 926 | #include 927 | 928 | static size_t pauser_count = 0; 929 | static size_t pausers_size = 0; 930 | static char* pausers = NULL; 931 | 932 | static int is_moc_playing(void) 933 | @{ 934 | return !WEXITSTATUS(system("env LANG=C mocp -i 2>/dev/null |" 935 | "grep 'State: PLAY' >/dev/null")); 936 | @} 937 | 938 | /* @w{@xrm{}In a proper implementation, message whould be copyied, and then@xtt{}} 939 | * @w{@xrm{}a new thread would be created that parsed the copy. But that is@xtt{}} 940 | * @w{@xrm{}too much for an example, especially since it would also require@xtt{}} 941 | * @w{@xrm{}a mutex to make sure two threads do not modify data at the same@xtt{}} 942 | * @w{@xrm{}time, causing chaos.@xtt{}} */ 943 | static int callback(const char *message, void *user_data) 944 | @{ 945 | char *msg = NULL; 946 | size_t len = 0; 947 | if (message == 0) 948 | return 1; 949 | while ((len < 2047) && message[len]) 950 | len++; 951 | msg = malloc((len + 1) * sizeof(char)); 952 | t (msg == NULL); 953 | memcpy(msg, message, len * sizeof(char)); 954 | msg[len] = 0; 955 | /* @w{@xrm{}BEGIN run as in a separate thread@xtt{}} */ 956 | if (pauser_count || is_moc_playing()) @{ 957 | char *begin = strchr(msg, ' '); 958 | ssize_t pid; 959 | int requests_pause; 960 | if (begin == NULL) 961 | goto done; 962 | *begin++ = 0; 963 | pid = (ssize_t)atoll(msg); 964 | if (pid < 1) /* @w{@xrm{}We need a real PID, too bad there is@xtt{}} 965 | * @w{@xrm{}no convient way to detect if it dies.@xtt{}} */ 966 | goto done; 967 | if ((strstr(begin, "force-pause ") == begin) || 968 | !strcmp(begin, "force-pause")) 969 | requests_pause = 1; 970 | else if ((strstr(begin, "unforce-pause ") == begin) || 971 | !strcmp(begin, "unforce-pause")) 972 | requests_pause = 0; 973 | else 974 | goto done; 975 | if ((size_t)pid >= pausers_size) @{ 976 | pausers = realloc(pausers, (size_t)(pid + 1) * sizeof(char)); 977 | t (pausers == NULL); /* @w{@xrm{}Let's ignore the memory leak.@xtt{}} */ 978 | memset(pausers + pausers_size, 0, 979 | ((size_t)(pid + 1) - pausers_size) * sizeof(char)); 980 | pausers_size = (size_t)(pid + 1); 981 | @} 982 | if (pausers[pid] ^ requests_pause) @{ 983 | pauser_count += requests_pause ? 1 : -1; 984 | pausers[pid] = requests_pause; 985 | if (pauser_count == (size_t)requests_pause) 986 | system(requests_pause ? "mocp -P" : "mocp -U"); 987 | @} 988 | @} 989 | /* @w{@xrm{}END run as in a separate thread@xtt{}} */ 990 | goto done; 991 | (void) user_data; 992 | 993 | fail: 994 | perror("monitor"); 995 | return -1; 996 | 997 | done: 998 | free(msg); 999 | return 1; 1000 | @} 1001 | 1002 | int main() 1003 | @{ 1004 | bus_t bus; 1005 | t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 1006 | t (bus_read(&bus, callback, NULL)); 1007 | bus_close(&bus); 1008 | free(pausers); 1009 | return 0; 1010 | 1011 | fail: 1012 | perror("monitor"); 1013 | bus_close(&bus); 1014 | free(pausers); 1015 | return 1; 1016 | @} 1017 | @end example 1018 | 1019 | 1020 | @subsubheading @file{./receive-or-make-call.c} 1021 | @example 1022 | #include 1023 | #include 1024 | #include 1025 | #include 1026 | 1027 | #define t(stmt) if (stmt) goto fail 1028 | 1029 | static char message[BUS_MEMORY_SIZE]; 1030 | 1031 | int main() 1032 | @{ 1033 | bus_t bus; 1034 | sprintf(message, "%ji force-pause", (intmax_t)getppid()); 1035 | /* @w{@xrm{}Yes, PPID; in this example we pretend the shell is the telephony process.@xtt{}} */ 1036 | t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); 1037 | t (bus_write(&bus, message, 0)); 1038 | bus_close(&bus); 1039 | return 0; 1040 | 1041 | fail: 1042 | perror("receive-or-make-call"); 1043 | bus_close(&bus); 1044 | return 1; 1045 | @} 1046 | @end example 1047 | 1048 | 1049 | 1050 | @node Timed 1051 | @section Timed 1052 | 1053 | This example shows how to use timed operations. 1054 | 1055 | First of, run make to build this example. 1056 | 1057 | To start the example run @command{./init}. When you are 1058 | done run @command{./cleanup}. 1059 | 1060 | Running instances of @command{./poll} will wait for new 1061 | messages continuously, but with one second timeouts. 1062 | 1063 | @command{./slow-poll} works like @command{./poll}, except 1064 | it will sleep for one second every time it receives 1065 | a message. 1066 | 1067 | Running instances of @command{./read} will read for ten 1068 | seconds and then time out. 1069 | 1070 | @command{./poll}, @command{./read}, and @command{./slow-poll} 1071 | will stop if the message "stop" is broadcasted. 1072 | 1073 | @command{./write} will wait for atmost a tenth of a 1074 | seconds before failing. This means that if two instances 1075 | of @command{./write} is started at the same time one of 1076 | them will fail if @command{./slow-poll} is running. 1077 | 1078 | @command{./poll}, @command{./read}, @command{./init} and 1079 | @command{./cleanup} are run without any additional 1080 | arguments. @command{./write} is run with the message 1081 | as the second argument. 1082 | 1083 | 1084 | @subsubheading @file{./Makefile} 1085 | @example 1086 | COMMANDS = init cleanup write poll read slow-poll 1087 | 1088 | all: $@{COMMANDS@} 1089 | %: %.c 1090 | $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< 1091 | clean: 1092 | -rm $@{COMMANDS@} 1093 | 1094 | .PHONY: all clean 1095 | @end example 1096 | 1097 | 1098 | @subsubheading @file{./cleanup.c} 1099 | @example 1100 | #include 1101 | #include 1102 | 1103 | int main() 1104 | @{ 1105 | return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); 1106 | @} 1107 | @end example 1108 | 1109 | 1110 | @subsubheading @file{./init.c} 1111 | @example 1112 | #include 1113 | #include 1114 | 1115 | int main() 1116 | @{ 1117 | return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); 1118 | @} 1119 | @end example 1120 | 1121 | 1122 | @subsubheading @file{./poll.c} 1123 | @example 1124 | #include 1125 | #include 1126 | #include 1127 | #include 1128 | #include 1129 | 1130 | #define t(stmt) if (stmt) goto fail 1131 | 1132 | int main() 1133 | @{ 1134 | bus_t bus; 1135 | const char *message; 1136 | long long tick = 0; 1137 | struct timespec timeout; 1138 | t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 1139 | t (bus_poll_start(&bus)); 1140 | for (;;) @{ 1141 | t (clock_gettime(CLOCK_MONOTONIC, &timeout)); 1142 | timeout.tv_sec += 1; 1143 | message = bus_poll_timed(&bus, &timeout, CLOCK_MONOTONIC); 1144 | if (message == NULL) @{ 1145 | t (errno != EAGAIN); 1146 | printf("waiting... %lli\n", ++tick); 1147 | continue; 1148 | @} 1149 | tick = 0; 1150 | message = strchr(message, ' ') + 1; 1151 | if (!strcmp(message, "stop")) 1152 | break; 1153 | printf("\033[01m%s\033[21m\n", message); 1154 | @} 1155 | t (bus_poll_stop(&bus)); 1156 | bus_close(&bus); 1157 | return 0; 1158 | 1159 | fail: 1160 | perror("poll"); 1161 | bus_poll_stop(&bus); 1162 | bus_close(&bus); 1163 | return 1; 1164 | @} 1165 | @end example 1166 | 1167 | 1168 | @subsubheading @file{./read.c} 1169 | @example 1170 | #include 1171 | #include 1172 | #include 1173 | #include 1174 | #include 1175 | 1176 | #define t(stmt) if (stmt) goto fail 1177 | 1178 | static int callback(const char *message, void *user_data) 1179 | @{ 1180 | (void) user_data; 1181 | 1182 | if (message == NULL) 1183 | return 1; 1184 | 1185 | message = strchr(message, ' ') + 1; 1186 | if (!strcmp(message, "stop")) 1187 | return 0; 1188 | printf("%s\n", message); 1189 | return 1; 1190 | @} 1191 | 1192 | int main() 1193 | @{ 1194 | bus_t bus; 1195 | struct timespec timeout; 1196 | t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 1197 | t (clock_gettime(CLOCK_MONOTONIC, &timeout)); 1198 | timeout.tv_sec += 10; 1199 | t (bus_read_timed(&bus, callback, NULL, &timeout, CLOCK_MONOTONIC)); 1200 | bus_close(&bus); 1201 | return 0; 1202 | 1203 | fail: 1204 | perror("poll"); 1205 | bus_poll_stop(&bus); 1206 | bus_close(&bus); 1207 | return 1; 1208 | @} 1209 | @end example 1210 | 1211 | 1212 | @subsubheading @file{./slow-poll.c} 1213 | @example 1214 | #include 1215 | #include 1216 | #include 1217 | #include 1218 | #include 1219 | 1220 | #define t(stmt) if (stmt) goto fail 1221 | 1222 | int main() 1223 | @{ 1224 | bus_t bus; 1225 | const char *message; 1226 | long long tick = 0; 1227 | struct timespec timeout; 1228 | t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 1229 | t (bus_poll_start(&bus)); 1230 | for (;;) @{ 1231 | t (clock_gettime(CLOCK_MONOTONIC, &timeout)); 1232 | timeout.tv_sec += 1; 1233 | message = bus_poll_timed(&bus, &timeout, CLOCK_MONOTONIC); 1234 | if (message == NULL) @{ 1235 | t (errno != EAGAIN); 1236 | printf("waiting... %lli\n", ++tick); 1237 | continue; 1238 | @} 1239 | tick = 0; 1240 | message = strchr(message, ' ') + 1; 1241 | if (!strcmp(message, "stop")) 1242 | break; 1243 | printf("\033[01m%s\033[21m\n", message); 1244 | sleep(1); 1245 | @} 1246 | t (bus_poll_stop(&bus)); 1247 | bus_close(&bus); 1248 | return 0; 1249 | 1250 | fail: 1251 | perror("poll"); 1252 | bus_poll_stop(&bus); 1253 | bus_close(&bus); 1254 | return 1; 1255 | @} 1256 | @end example 1257 | 1258 | 1259 | @subsubheading @file{./write.c} 1260 | @example 1261 | #include 1262 | #include 1263 | #include 1264 | #include 1265 | 1266 | #define t(stmt) if (stmt) goto fail 1267 | 1268 | static char message[BUS_MEMORY_SIZE]; 1269 | 1270 | int main(int argc, char *argv[]) 1271 | @{ 1272 | bus_t bus; 1273 | struct timespec timeout; 1274 | if (argc < 2) @{ 1275 | fprintf(stderr, "%s: USAGE: %s message\n", argv[0], argv[0]); 1276 | return 2; 1277 | @} 1278 | sprintf(message, "0 %s", argv[1]); 1279 | t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); 1280 | t (clock_gettime(CLOCK_MONOTONIC, &timeout)); 1281 | timeout.tv_nsec += 100000000L; 1282 | t (bus_write_timed(&bus, message, &timeout, CLOCK_MONOTONIC)); 1283 | bus_close(&bus); 1284 | return 0; 1285 | 1286 | fail: 1287 | perror("write"); 1288 | bus_close(&bus); 1289 | return 1; 1290 | @} 1291 | @end example 1292 | 1293 | 1294 | 1295 | @node Nonblocking 1296 | @section Nonblocking 1297 | 1298 | This example shows how to use bus_poll instead of bus_read, 1299 | and how to do non-blocking polling and non-blocking writing. 1300 | 1301 | First of, run make to build this example. 1302 | 1303 | To start the example run @command{./init}. When you are done 1304 | run @command{./cleanup}. 1305 | 1306 | Running instances of @command{./poll} will check every second 1307 | if there is a new inbound message. Between these checks 1308 | @command{./write} will wait for all @command{./poll}:s to 1309 | receive the message. This means that @command{./write} blocks 1310 | while @command{./poll} sleeps. If two or more instances of 1311 | @command{./write} is started at approximately the same time, 1312 | only one will continue to write a message on the bus, the 1313 | others will fail. 1314 | 1315 | @command{./poll} will stop if the message ``stop'' is 1316 | broadcasted. 1317 | 1318 | @command{./poll}, @command{./init} and @command{./cleanup} 1319 | are run without any additional arguments. @command{./write} 1320 | is run with the message as the second argument. 1321 | 1322 | 1323 | @subsubheading @file{./Makefile} 1324 | @example 1325 | COMMANDS = init cleanup write poll 1326 | 1327 | all: $@{COMMANDS@} 1328 | %: %.c 1329 | $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< 1330 | clean: 1331 | -rm $@{COMMANDS@} 1332 | 1333 | .PHONY: all clean 1334 | @end example 1335 | 1336 | 1337 | @subsubheading @file{./cleanup.c} 1338 | @example 1339 | #include 1340 | #include 1341 | 1342 | int main() 1343 | @{ 1344 | return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); 1345 | @} 1346 | @end example 1347 | 1348 | 1349 | @subsubheading @file{./init.c} 1350 | @example 1351 | #include 1352 | #include 1353 | 1354 | int main() 1355 | @{ 1356 | return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); 1357 | @} 1358 | @end example 1359 | 1360 | 1361 | @subsubheading @file{./poll.c} 1362 | @example 1363 | #include 1364 | #include 1365 | #include 1366 | #include 1367 | #include 1368 | 1369 | #define t(stmt) if (stmt) goto fail 1370 | 1371 | int main() 1372 | @{ 1373 | bus_t bus; 1374 | const char *message; 1375 | long long tick = 0; 1376 | t (bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 1377 | t (bus_poll_start(&bus)); 1378 | for (;;) @{ 1379 | message = bus_poll(&bus, BUS_NOWAIT); 1380 | if (message == NULL) @{ 1381 | t (errno != EAGAIN); 1382 | printf("waiting... %lli\n", ++tick); 1383 | sleep(1); 1384 | continue; 1385 | @} 1386 | tick = 0; 1387 | message = strchr(message, ' ') + 1; 1388 | if (!strcmp(message, "stop")) 1389 | break; 1390 | printf("\033[01m%s\033[21m\n", message); 1391 | @} 1392 | t (bus_poll_stop(&bus)); 1393 | bus_close(&bus); 1394 | return 0; 1395 | 1396 | fail: 1397 | perror("poll"); 1398 | bus_poll_stop(&bus); 1399 | bus_close(&bus); 1400 | return 1; 1401 | @} 1402 | @end example 1403 | 1404 | 1405 | @subsubheading @file{./write.c} 1406 | @example 1407 | #include 1408 | #include 1409 | #include 1410 | #include 1411 | 1412 | #define t(stmt) if (stmt) goto fail 1413 | 1414 | static char message[BUS_MEMORY_SIZE]; 1415 | 1416 | int main(int argc, char *argv[]) 1417 | @{ 1418 | bus_t bus; 1419 | if (argc < 2) @{ 1420 | fprintf(stderr, "%s: USAGE: %s message\n", argv[0], argv[0]); 1421 | return 2; 1422 | @} 1423 | sprintf(message, "0 %s", argv[1]); 1424 | t (bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); 1425 | t (bus_write(&bus, message, BUS_NOWAIT)); 1426 | bus_close(&bus); 1427 | return 0; 1428 | 1429 | fail: 1430 | perror("write"); 1431 | bus_close(&bus); 1432 | return 1; 1433 | @} 1434 | @end example 1435 | 1436 | 1437 | 1438 | @node Daemon-dependencies 1439 | @section Daemon-dependencies 1440 | 1441 | This example shows how bus can be used in an init system 1442 | to provide ``aggressivly'' parallel startup of daemons. 1443 | 1444 | First of, run make to build this example. 1445 | 1446 | To start the example run @command{./init}. It will print in 1447 | red export-statement you may want to run i other terminals. 1448 | You will need to select at least one daemon, for example 1449 | you can run @code{./init d-ntp}. The available pretend 1450 | daemons are: @command{d-network}, @command{d-ntp}, and 1451 | @command{d-ssh}. 1452 | 1453 | When you are done run @command{./cleanup} with @env{BUS_INIT} 1454 | exported with the value printed by @command{./init}. 1455 | 1456 | 1457 | @subsubheading @file{./Makefile} 1458 | @example 1459 | COMMANDS = announce await-ready await-started cleanup \ 1460 | init require start-daemon test-daemon 1461 | 1462 | all: $@{COMMANDS@} 1463 | %: %.c 1464 | $@{CC@} -Wall -Wextra -pedantic -std=c99 -lbus -o $@@ $< 1465 | clean: 1466 | -rm $@{COMMANDS@} 1467 | -rm -r run 1468 | 1469 | .PHONY: all clean 1470 | @end example 1471 | 1472 | 1473 | @subsubheading @file{./announce.c} 1474 | @example 1475 | #include 1476 | #include 1477 | #include 1478 | #include 1479 | #include 1480 | 1481 | #define t(stmt) if (stmt) goto fail 1482 | 1483 | static char arg[4098]; 1484 | 1485 | int main(int argc, char *argv[]) 1486 | @{ 1487 | bus_t bus; 1488 | if (argc < 3) 1489 | return fprintf(stderr, "USAGE: %s state daemon", *argv), 2; 1490 | t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 1491 | sprintf(arg, "%ji %s %s", (intmax_t)getppid(), argv[1], argv[2]); 1492 | t (bus_write(&bus, arg, 0)); 1493 | t (bus_close(&bus)); 1494 | return 0; 1495 | 1496 | fail: 1497 | perror("announce"); 1498 | return 1; 1499 | @} 1500 | @end example 1501 | 1502 | 1503 | @subsubheading @file{./await-ready.c} 1504 | @example 1505 | #include 1506 | #include 1507 | #include 1508 | #include 1509 | #include 1510 | #include 1511 | #include 1512 | 1513 | #define t(stmt) if (stmt) goto fail 1514 | 1515 | static char arg[4098]; 1516 | static int argc; 1517 | static char **argv; 1518 | static int remaining = 0; 1519 | static char *started = NULL; 1520 | static char msg[BUS_MEMORY_SIZE]; 1521 | 1522 | static void announce_wait(pid_t pid) 1523 | @{ 1524 | bus_t bus; 1525 | int i; 1526 | t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 1527 | for (i = 1; i < argc; i++) @{ 1528 | if (!started[i]) @{ 1529 | sprintf(arg, "%ji awaiting-ready %s", (intmax_t)pid, argv[i]); 1530 | t (bus_write(&bus, arg, 0)); 1531 | @} 1532 | @} 1533 | t (bus_close(&bus)); 1534 | return; 1535 | 1536 | fail: 1537 | perror("await-ready"); 1538 | @} 1539 | 1540 | static int callback(const char *message, void *user_data) 1541 | @{ 1542 | int i; 1543 | char *arg2; 1544 | char *arg3; 1545 | pid_t pid; 1546 | pid_t ppid; 1547 | 1548 | if (!message) @{ 1549 | ppid = getppid(); 1550 | pid = fork(); 1551 | if (pid == 0) @{ 1552 | if (fork() == 0) 1553 | announce_wait(ppid); 1554 | exit(0); 1555 | @} else @{ 1556 | (void) waitpid(pid, NULL, 0); 1557 | /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ 1558 | @} 1559 | return 1; 1560 | @} 1561 | 1562 | strncpy(msg, message, BUS_MEMORY_SIZE - 1); 1563 | msg[BUS_MEMORY_SIZE - 1] = 0; 1564 | 1565 | arg2 = strchr(msg, ' '); 1566 | if (!arg2) 1567 | return 1; 1568 | arg3 = strchr(++arg2, ' '); 1569 | if (!arg3) 1570 | return 1; 1571 | *arg3++ = 0; 1572 | 1573 | if (strcmp(arg2, "ready")) 1574 | return 1; 1575 | 1576 | for (i = 1; i < argc; i++) 1577 | if (!started[i] && !strcmp(argv[i], arg3)) 1578 | started[i] = 1, remaining--; 1579 | 1580 | return !!remaining; 1581 | (void) user_data; 1582 | @} 1583 | 1584 | int main(int argc_, char *argv_[]) 1585 | @{ 1586 | bus_t bus; 1587 | int i; 1588 | 1589 | argc = argc_; 1590 | argv = argv_; 1591 | 1592 | if (argc < 2) 1593 | return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; 1594 | t (bus_open(&bus, getenv("BUS_INIT"), BUS_RDONLY)); 1595 | started = calloc(argc, sizeof(char)); 1596 | t (started == NULL); 1597 | 1598 | started[0] = 1; 1599 | for (i = 1; i < argc; i++) @{ 1600 | sprintf(arg, "grep '^%s$'" 1601 | " <\"$@{XDG_RUNTIME_DIR@}/ready-daemons\"" 1602 | " >/dev/null", 1603 | argv[i]); 1604 | if (!WEXITSTATUS(system(arg))) 1605 | started[i] = 1; 1606 | else 1607 | remaining++; 1608 | @} 1609 | 1610 | if (remaining) 1611 | bus_read(&bus, callback, NULL); 1612 | 1613 | bus_close(&bus); 1614 | free(started); 1615 | return 0; 1616 | 1617 | fail: 1618 | perror("await-ready"); 1619 | bus_close(&bus); 1620 | free(started); 1621 | return 1; 1622 | @} 1623 | @end example 1624 | 1625 | 1626 | @subsubheading @file{./await-started.c} 1627 | @example 1628 | #include 1629 | #include 1630 | #include 1631 | #include 1632 | #include 1633 | #include 1634 | #include 1635 | 1636 | #define t(stmt) if (stmt) goto fail 1637 | 1638 | static char arg[4098]; 1639 | static int argc; 1640 | static char **argv; 1641 | static int remaining = 0; 1642 | static char *started = NULL; 1643 | static char msg[BUS_MEMORY_SIZE]; 1644 | 1645 | static void announce_wait(pid_t pid) 1646 | @{ 1647 | bus_t bus; 1648 | int i; 1649 | t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 1650 | for (i = 1; i < argc; i++) @{ 1651 | if (!started[i]) @{ 1652 | sprintf(arg, "%ji awaiting-started %s", (intmax_t)pid, argv[i]); 1653 | t (bus_write(&bus, arg, 0)); 1654 | @} 1655 | @} 1656 | t (bus_close(&bus)); 1657 | return; 1658 | 1659 | fail: 1660 | perror("await-started"); 1661 | @} 1662 | 1663 | static int callback(const char *message, void *user_data) 1664 | @{ 1665 | int i; 1666 | char *arg2; 1667 | char *arg3; 1668 | pid_t pid; 1669 | pid_t ppid; 1670 | 1671 | if (!message) @{ 1672 | ppid = getppid(); 1673 | pid = fork(); 1674 | if (pid == 0) @{ 1675 | if (fork() == 0) 1676 | announce_wait(ppid); 1677 | exit(0); 1678 | @} else @{ 1679 | (void) waitpid(pid, NULL, 0); 1680 | /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ 1681 | @} 1682 | return 1; 1683 | @} 1684 | 1685 | strncpy(msg, message, BUS_MEMORY_SIZE - 1); 1686 | msg[BUS_MEMORY_SIZE - 1] = 0; 1687 | 1688 | arg2 = strchr(msg, ' '); 1689 | if (!arg2) 1690 | return 1; 1691 | arg3 = strchr(++arg2, ' '); 1692 | if (!arg3) 1693 | return 1; 1694 | *arg3++ = 0; 1695 | 1696 | if (strcmp(arg2, "started") && strcmp(arg2, "ready")) 1697 | return 1; 1698 | 1699 | for (i = 1; i < argc; i++) 1700 | if (!started[i] && !strcmp(argv[i], arg3)) 1701 | started[i] = 1, remaining--; 1702 | 1703 | return !!remaining; 1704 | (void) user_data; 1705 | @} 1706 | 1707 | int main(int argc_, char *argv_[]) 1708 | @{ 1709 | bus_t bus; 1710 | int i; 1711 | 1712 | argc = argc_; 1713 | argv = argv_; 1714 | 1715 | if (argc < 2) 1716 | return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; 1717 | t (bus_open(&bus, getenv("BUS_INIT"), BUS_RDONLY)); 1718 | started = calloc(argc, sizeof(char)); 1719 | t (started == NULL); 1720 | 1721 | started[0] = 1; 1722 | for (i = 1; i < argc; i++) @{ 1723 | sprintf(arg, "grep '^%s$'" 1724 | " <\"$@{XDG_RUNTIME_DIR@}/started-daemons\"" 1725 | " >/dev/null", 1726 | argv[i]); 1727 | if (!WEXITSTATUS(system(arg))) @{ 1728 | started[i] = 1; 1729 | @} else @{ 1730 | sprintf(arg, "grep '^%s$'" 1731 | " <\"$@{XDG_RUNTIME_DIR@}/ready-daemons\"" 1732 | " >/dev/null", 1733 | argv[i]); 1734 | if (!WEXITSTATUS(system(arg))) 1735 | started[i] = 1; 1736 | else 1737 | remaining++; 1738 | @} 1739 | @} 1740 | 1741 | if (remaining) 1742 | bus_read(&bus, callback, NULL); 1743 | 1744 | bus_close(&bus); 1745 | free(started); 1746 | return 0; 1747 | 1748 | fail: 1749 | perror("await-started"); 1750 | bus_close(&bus); 1751 | free(started); 1752 | return 1; 1753 | @} 1754 | @end example 1755 | 1756 | 1757 | @subsubheading @file{./cleanup.c} 1758 | @example 1759 | #include 1760 | #include 1761 | #include 1762 | 1763 | #define t(stmt) if (stmt) goto fail 1764 | 1765 | int main() 1766 | @{ 1767 | char *bus_address = getenv("BUS_INIT"); 1768 | if (!bus_address || !*bus_address) @{ 1769 | fprintf(stderr, "$BUS_INIT has not been set, its export statement " 1770 | "should have been printed in bold red by ./init\n"); 1771 | return 1; 1772 | @} 1773 | t (bus_unlink(bus_address)); 1774 | return 0; 1775 | 1776 | fail: 1777 | perror("cleanup"); 1778 | return 1; 1779 | @} 1780 | @end example 1781 | 1782 | 1783 | @subsubheading @file{./d-network} 1784 | @example 1785 | #!/bin/sh 1786 | PATH=.:$PATH 1787 | d=d-network 1788 | 1789 | echo $d: starting 1790 | sleep 2 1791 | echo $d: ready 1792 | announce ready $d 1793 | @end example 1794 | 1795 | 1796 | @subsubheading @file{./d-ntp} 1797 | @example 1798 | #!/bin/sh 1799 | PATH=.:$PATH 1800 | d=d-ntp 1801 | 1802 | require d-network 1803 | echo $d: started 1804 | announce started $d 1805 | await-ready d-network 1806 | echo $d: ready 1807 | announce ready $d 1808 | @end example 1809 | 1810 | 1811 | @subsubheading @file{./d-ssh} 1812 | @example 1813 | #!/bin/sh 1814 | PATH=.:$PATH 1815 | d=d-ssh 1816 | 1817 | require d-network 1818 | echo $d: starting 1819 | sleep 1 1820 | echo $d: started 1821 | announce started $d 1822 | sleep 1 1823 | echo $d: ready 1824 | announce ready $d 1825 | @end example 1826 | 1827 | 1828 | @subsubheading @file{./init.c} 1829 | @example 1830 | #include 1831 | #include 1832 | #include 1833 | #include 1834 | #include 1835 | #include 1836 | 1837 | #define t(stmt) if (stmt) goto fail 1838 | #define _2(...) __VA_ARGS__, __VA_ARGS__ 1839 | 1840 | static char msg[BUS_MEMORY_SIZE]; 1841 | static int argc; 1842 | static char **argv; 1843 | static char arg[4098]; 1844 | 1845 | static void start_daemons() 1846 | @{ 1847 | int i; 1848 | for (i = 1; i < argc; i++) 1849 | if (fork() == 0) 1850 | execl("./start-daemon", "./start-daemon", argv[i], NULL); 1851 | @} 1852 | 1853 | static int callback(const char *message, void *user_data) 1854 | @{ 1855 | pid_t pid; 1856 | char *arg2; 1857 | char *arg3; 1858 | if (!message) @{ 1859 | pid = fork(); 1860 | t (pid == -1); 1861 | if (pid == 0) @{ 1862 | if (fork() == 0) @{ 1863 | start_daemons(); 1864 | @} 1865 | exit(0); 1866 | @} else @{ 1867 | (void) waitpid(pid, NULL, 0); 1868 | /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ 1869 | @} 1870 | return 1; 1871 | @} 1872 | 1873 | strncpy(msg, message, BUS_MEMORY_SIZE - 1); 1874 | msg[BUS_MEMORY_SIZE - 1] = 0; 1875 | 1876 | pid = fork(); 1877 | t (pid == -1); 1878 | 1879 | if (pid == 0) @{ 1880 | if (fork() == 0) @{ 1881 | arg2 = strchr(msg, ' '); 1882 | if (arg2 == NULL) 1883 | exit(0); 1884 | arg3 = strchr(++arg2, ' '); 1885 | if (arg3 == NULL) 1886 | exit(0); 1887 | *arg3++ = 0; 1888 | if (!strcmp(arg2, "require")) @{ 1889 | execl(_2("./start-daemon"), arg3, NULL); 1890 | @} else if (!strcmp(arg2, "awaiting-started")) @{ 1891 | execl(_2("./test-daemon"), arg3, "started", NULL); 1892 | @} else if (!strcmp(arg2, "awaiting-ready") || 1893 | !strcmp(arg2, "awaiting")) @{ 1894 | execl(_2("./test-daemon"), arg3, "ready", NULL); 1895 | @} else if (!strcmp(arg2, "started")) @{ 1896 | sprintf(arg, 1897 | "grep '^%s\\$' < \"%s\" >/dev/null || echo %s >> \"%s\"", 1898 | _2(arg3, "$@{XDG_RUNTIME_DIR@}/started-daemons")); 1899 | execlp(_2("sh"), "-c", arg, NULL); 1900 | @} else if (!strcmp(arg2, "ready")) @{ 1901 | sprintf(arg, 1902 | "grep '^%s\\$' < \"%s\" >/dev/null || echo %s >> \"%s\"", 1903 | _2(arg3, "$@{XDG_RUNTIME_DIR@}/ready-daemons")); 1904 | execlp(_2("sh"), "-c", arg, NULL); 1905 | @} 1906 | @} 1907 | exit(0); 1908 | @} else @{ 1909 | (void) waitpid(pid, NULL, 0); 1910 | /* @w{@xrm{}Let's pretend everything will go swimmingly.@xtt{}} */ 1911 | @} 1912 | 1913 | return 1; 1914 | (void) user_data; 1915 | 1916 | fail: 1917 | perror("init"); 1918 | return -1; 1919 | @} 1920 | 1921 | int main(int argc_, char *argv_[]) 1922 | @{ 1923 | char *bus_address = NULL; 1924 | bus_t bus; 1925 | argv = argv_; 1926 | argc = argc_; 1927 | if (argc < 2) @{ 1928 | fprintf(stderr, "USAGE: %s daemon...\n", *argv); 1929 | return 1; 1930 | @} 1931 | t (setenv("XDG_RUNTIME_DIR", "./run", 1)); 1932 | /* @w{@xrm{}Real init systems with not have the period.@xtt{}} */ 1933 | system("mkdir -p -- \"$@{XDG_RUNTIME_DIR@}\""); 1934 | system("truncate -s 0 -- \"$@{XDG_RUNTIME_DIR@}/started-daemons\""); 1935 | system("truncate -s 0 -- \"$@{XDG_RUNTIME_DIR@}/ready-daemons\""); 1936 | t (bus_create(NULL, 1, &bus_address)); 1937 | fprintf(stderr, "\033[00;01;31mexport BUS_INIT=%s\033[00m\n", 1938 | bus_address); 1939 | fprintf(stderr, "\033[00;31mexport XDG_RUNTIME_DIR=./run\033[00m\n"); 1940 | t (setenv("BUS_INIT", bus_address, 1)); 1941 | t (bus_open(&bus, bus_address, BUS_RDONLY)); 1942 | t (bus_read(&bus, callback, NULL)); 1943 | bus_close(&bus); 1944 | free(bus_address); 1945 | return 0; 1946 | 1947 | fail: 1948 | perror("init"); 1949 | bus_close(&bus); 1950 | free(bus_address); 1951 | return 1; 1952 | @} 1953 | @end example 1954 | 1955 | 1956 | @subsubheading @file{./require.c} 1957 | @example 1958 | #include 1959 | #include 1960 | #include 1961 | #include 1962 | #include 1963 | 1964 | #define t(stmt) if (stmt) goto fail 1965 | 1966 | static char arg[4098]; 1967 | 1968 | int main(int argc, char *argv[]) 1969 | @{ 1970 | bus_t bus; 1971 | int i; 1972 | if (argc < 2) 1973 | return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; 1974 | t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 1975 | 1976 | for (i = 1; i < argc; i++) @{ 1977 | sprintf(arg, "grep '^%s$' <\"%s\" >/dev/null", 1978 | argv[i], "$@{XDG_RUNTIME_DIR@}/started-daemons"); 1979 | if (WEXITSTATUS(system(arg))) @{ 1980 | sprintf(arg, "%ji require %s", (intmax_t)getppid(), argv[i]); 1981 | t (bus_write(&bus, arg, 0)); 1982 | @} 1983 | @} 1984 | 1985 | bus_close(&bus); 1986 | return 0; 1987 | 1988 | fail: 1989 | perror("require"); 1990 | bus_close(&bus); 1991 | return 1; 1992 | @} 1993 | @end example 1994 | 1995 | 1996 | @subsubheading @file{./start-daemon.c} 1997 | @example 1998 | #include 1999 | #include 2000 | #include 2001 | #include 2002 | #include 2003 | #include 2004 | 2005 | static char arg[4098]; 2006 | 2007 | int main(int argc, char *argv[]) 2008 | @{ 2009 | if (argc != 2) @{ 2010 | fprintf(stderr, "This program should be called from ./init\n"); 2011 | return 2; 2012 | @} 2013 | 2014 | sprintf(arg, "grep '^%s$' <\"%s\" >/dev/null", 2015 | argv[1], "$@{XDG_RUNTIME_DIR@}/started-daemons"); 2016 | if (!WEXITSTATUS(system(arg))) 2017 | return 0; 2018 | sprintf(arg, "grep '^%s$' <\"%s\" >/dev/null", 2019 | argv[1], "$@{XDG_RUNTIME_DIR@}/ready-daemons"); 2020 | if (!WEXITSTATUS(system(arg))) 2021 | return 0; 2022 | 2023 | sprintf(arg, "./%s", argv[1]); 2024 | execlp(arg, arg, NULL); 2025 | perror("start-daemon"); 2026 | return 1; 2027 | @} 2028 | @end example 2029 | 2030 | 2031 | @subsubheading @file{./test-daemon.c} 2032 | @example 2033 | #include 2034 | #include 2035 | #include 2036 | #include 2037 | 2038 | #define t(stmt) if (stmt) goto fail 2039 | 2040 | static char arg[4098]; 2041 | 2042 | int main(int argc, char *argv[]) 2043 | @{ 2044 | bus_t bus; 2045 | if (argc != 3) @{ 2046 | fprintf(stderr, "This program should be called from ./init\n"); 2047 | return 2; 2048 | @} 2049 | 2050 | retry: 2051 | sprintf(arg, "grep '^%s$'" 2052 | " <\"$@{XDG_RUNTIME_DIR@}/%s-daemons\"" 2053 | " >/dev/null", 2054 | argv[1], argv[2]); 2055 | if (!WEXITSTATUS(system(arg))) @{ 2056 | t (bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 2057 | sprintf(arg, "0 %s %s", argv[2], argv[1]); 2058 | t (bus_write(&bus, arg, 0)); 2059 | bus_close(&bus); 2060 | @} else if (!strcmp(argv[2], "started")) @{ 2061 | argv[2] = "ready"; 2062 | goto retry; 2063 | @} 2064 | return 0; 2065 | 2066 | fail: 2067 | perror("test-daemon"); 2068 | return 1; 2069 | @} 2070 | @end example 2071 | 2072 | 2073 | 2074 | @node GNU Free Documentation License 2075 | @appendix GNU Free Documentation License 2076 | @include fdl.texinfo 2077 | 2078 | @bye 2079 | 2080 | -------------------------------------------------------------------------------- /bus_chmod.3: -------------------------------------------------------------------------------- 1 | .TH BUS_CHMOD 3 BUS 2 | .SH NAME 3 | bus_chmod - Change bus mode bits 4 | .SH SYNOPSIS 5 | .LP 6 | .nf 7 | #include 8 | .P 9 | int bus_chmod(const char *\fIfile\fP, mode_t \fImode\fP); 10 | .fi 11 | .SH DESCRIPTION 12 | The 13 | .BR bus_chmod () 14 | function gives access to the bus associated with \fIfile\fP 15 | according to the following rules: 16 | .TP 17 | * 18 | If (\fImode\fP &S_IRWXU) the owner should be given full access to the 19 | bus. Otherwise the owner should have no access. 20 | .TP 21 | * 22 | If (\fImode\fP &S_IRWXG) the group should be given read and write 23 | access to the bus. Otherwise the group should have no access. 24 | .TP 25 | * 26 | If (\fImode\fP &S_IRWXO) others (except the group) should be given 27 | read and write access to the bus. Otherwise others should have no 28 | access. 29 | .PP 30 | The current permissions of a bus can be retrieved by running 31 | .BR stat (3) 32 | over the \fIpathname\fP of the bus. 33 | .SH RETURN VALUES 34 | Upon successful completion, the function returns 0. Otherwise the 35 | function returns -1 and sets \fIerrno\fP to indicate the error. 36 | .SH ERRORS 37 | The 38 | .BR bus_chown (3) 39 | function may fail and set \fIerrno\fP to any of the 40 | errors specified for 41 | .BR bus_open (3), 42 | .BR chmod (3), 43 | .BR semget (3), 44 | .BR shmget (3) 45 | and 46 | .BR shmctl (3) 47 | as well as any errors specified for the \fIIPC_STAT\fP and 48 | \fIIPC_SET\fP commands for 49 | .BR semctl (3). 50 | .SH SEE ALSO 51 | .BR bus-create (1), 52 | .BR bus (5), 53 | .BR libbus (7), 54 | .BR bus_open (3), 55 | .BR bus_read (3), 56 | .BR stat (3) 57 | -------------------------------------------------------------------------------- /bus_chown.3: -------------------------------------------------------------------------------- 1 | .TH BUS_CHOWN 3 BUS 2 | .SH NAME 3 | bus_chown - Change bus owner and group 4 | .SH SYNOPSIS 5 | .LP 6 | .nf 7 | #include 8 | .P 9 | int bus_chown(const char *\fIfile\fP, uid_t \fIowner\fP, gid_t \fIgroup\fP); 10 | .fi 11 | .SH DESCRIPTION 12 | The 13 | .BR bus_chown () 14 | function changes the owner and the group of the bus associated with 15 | \fIfile\fP to the \fIowner\fP and \fIgroup\fP, respectively. 16 | .PP 17 | The current ownership of a bus can be retrieved by running 18 | .BR stat (3) 19 | over the \fIpathname\fP of the bus. 20 | .SH RETURN VALUES 21 | Upon successful completion, the function returns 0. Otherwise the 22 | function returns -1 and sets \fIerrno\fP to indicate the error. 23 | .SH ERRORS 24 | The 25 | .BR bus_chown (3) 26 | function may fail and set \fIerrno\fP to any of the 27 | errors specified for 28 | .BR bus_open (3), 29 | .BR chown (3), 30 | .BR semget (3), 31 | .BR shmget (3) 32 | and 33 | .BR shmctl (3) 34 | as well as any errors specified for the \fIIPC_STAT\fP and 35 | \fIIPC_SET\fP commands for 36 | .BR semctl (3). 37 | .SH SEE ALSO 38 | .BR bus-create (1), 39 | .BR bus (5), 40 | .BR libbus (7), 41 | .BR bus_open (3), 42 | .BR bus_read (3), 43 | .BR stat (3) 44 | -------------------------------------------------------------------------------- /bus_close.3: -------------------------------------------------------------------------------- 1 | .TH BUS_CLOSE 3 BUS 2 | .SH NAME 3 | bus_close - Close a bus 4 | .SH SYNOPSIS 5 | .LP 6 | .nf 7 | #include 8 | .P 9 | int bus_close(bus_t *\fIbus\fP); 10 | .fi 11 | .SH DESCRIPTION 12 | The 13 | .BR bus_close () 14 | function disposes of resources allocated to the process, as referenced 15 | in the \fIbus\fP argument. 16 | .SH RETURN VALUES 17 | Upon successful completion, the function returns 0. Otherwise the 18 | function returns -1 and sets \fIerrno\fP to indicate the error. 19 | .SH ERRORS 20 | .TP 21 | .B EINVAL 22 | The bus does not exist. 23 | .SH SEE ALSO 24 | .BR bus-create (1), 25 | .BR bus (5), 26 | .BR libbus (7), 27 | .BR bus_open (3), 28 | .BR bus_unlink (3) 29 | -------------------------------------------------------------------------------- /bus_create.3: -------------------------------------------------------------------------------- 1 | .TH BUS_CREATE 3 BUS 2 | .SH NAME 3 | bus_create - Create a new bus 4 | .SH SYNOPSIS 5 | .LP 6 | .nf 7 | #include 8 | .P 9 | int bus_create(const char *\fIfile\fP, int \fIflags\fP, char **\fIout_file\fP); 10 | .fi 11 | .SH DESCRIPTION 12 | The 13 | .BR bus_create () 14 | function creates a bus with the asscoiated pathname specifed by the 15 | value of the parameter \fIfile\fP. If \fIfile\fP is \fINULL\fP a random 16 | pathname is selected. This pathname adheres to the convention set forth 17 | by 18 | .BR bus (5). 19 | .PP 20 | If \fIfile\fP is not \fINULL\fP the 21 | .BR bus_create () 22 | function fails if the file already exists if \fIflags\fP contains 23 | \fIBUS_EXCL\fP. Otherwise if \fIfile\fP is not \fINULL\fP, the 24 | .BR bus_create () 25 | function does nothing if the file already exists. 26 | .PP 27 | If \fIflags\fP contains \fIBUS_INTR\fP, the function fails if it is 28 | interrupted. 29 | .PP 30 | Unless \fIout_file\fP is \fINULL\fP, the pathname of the bus should be 31 | stored in a new char array stored in \fI*out_file\fP. The caller must 32 | free the allocated stored in \fI*out_file\fP. 33 | .SH RETURN VALUES 34 | Upon successful completion, the function returns 0. Otherwise the 35 | function return -1 with \fIerrno\fP set to indicate the error. 36 | .SH ERRORS 37 | .TP 38 | .B ENOMEM 39 | The process cannot allocate more memory. 40 | .PP 41 | The 42 | .BR bus_create (3) 43 | function may also fail and set \fIerrno\fP to any 44 | of the errors specified for the routines 45 | .BR open (2) 46 | and 47 | .BR write (2). 48 | .SH SEE ALSO 49 | .BR bus-create (1), 50 | .BR bus (5), 51 | .BR libbus (7), 52 | .BR bus_unlink (3), 53 | .BR bus_open (3), 54 | .BR open (2), 55 | .BR write (2) 56 | -------------------------------------------------------------------------------- /bus_open.3: -------------------------------------------------------------------------------- 1 | .TH BUS_OPEN 3 BUS 2 | .SH NAME 3 | bus_open - Open a bus 4 | .SH SYNOPSIS 5 | .LP 6 | .nf 7 | #include 8 | .P 9 | int bus_open(bus_t *\fIbus\fP, const char *\fIfile\fP, int \fIflags\fP); 10 | .fi 11 | .SH DESCRIPTION 12 | The 13 | .BR bus_open () 14 | function acquires resources required for the process to use the bus 15 | associated with the filename stored in \fIfile\fP. The function also 16 | stores the resources in \fIbus\fP for use by other 17 | .BR bus 18 | functions. 19 | .PP 20 | Values for \fIflags\fP are constructed by a bitwise inclusive OR of 21 | flags from the following list. 22 | .TP 23 | .B BUS_RDONLY 24 | The process will only be using the bus for receiving messages. 25 | .TP 26 | .B BUS_WRONLY 27 | The process will only be using the bus for sending messages. 28 | .TP 29 | .B BUS_RDWR 30 | The process will use the bus for both receiving and sending messages. 31 | .SH RETURN VALUES 32 | Upon successful completion the function returns 0. Otherwise the 33 | function returns -1 and set \fIerrno\fP to indicate the error. 34 | .SH ERRORS 35 | .TP 36 | .B ENOMEM 37 | The process cannot allocate more memory. 38 | .TP 39 | .B EACCES 40 | Operation permission is denied to the calling process. 41 | .TP 42 | .B EINVAL 43 | The described bus does not exist. 44 | .PP 45 | The 46 | .BR bus_open () 47 | function may also fail and set \fIerrno\fP to any of the errors 48 | specified for the routine 49 | .BR open (2). 50 | .SH SEE ALSO 51 | .BR bus-create (1), 52 | .BR bus (5), 53 | .BR libbus (7), 54 | .BR bus_close (3), 55 | .BR bus_unlink (3), 56 | .BR bus_write (3), 57 | .BR bus_read (3), 58 | .BR bus_poll (3), 59 | .BR open (2) 60 | -------------------------------------------------------------------------------- /bus_poll.3: -------------------------------------------------------------------------------- 1 | .TH BUS_POLL 3 BUS 2 | .SH NAME 3 | bus_poll_start, bus_poll_stop, bus_poll, bus_poll_timed - Wait a message to be broadcasted 4 | .SH SYNOPSIS 5 | .LP 6 | .nf 7 | #include 8 | .P 9 | int bus_poll_start(bus_t *\fIbus\fP); 10 | int bus_poll_stop(const bus_t *\fIbus\fP); 11 | const char *bus_poll(bus_t *\fIbus\fP, int \fIflags\fP); 12 | const char *bus_poll_timed(bus_t *\fIbus\fP, const struct timespec *\fItimeout\fP, clockid_t \fIclockid\fP); 13 | .fi 14 | .SH DESCRIPTION 15 | The 16 | .BR bus_poll () 17 | function waits for a message to be broadcasted on the \fIbus\fP, and return 18 | the message it receives. The function fails if (\fIflags\fP &BUS_NOWAIT) 19 | and there is not already a message waiting on the bus. Received messages 20 | shall be copied and parsed, and acted upon, in a separate thread, and 21 | .BR bus_poll () 22 | or 23 | .BR bus_poll_stop () 24 | called again as soon as possible. 25 | .PP 26 | The 27 | .BR bus_poll_start () 28 | funcion must be called before 29 | .BR bus_poll () 30 | is called for the first time. When the process is done listening on the 31 | bus it must call the 32 | .BR bus_poll_stop () 33 | function. 34 | .PP 35 | The 36 | .BR bus_poll_timed () 37 | function behaves like 38 | .BR bus_poll (), 39 | except if it is not able to read a message within the specified time, 40 | it will fail and set \fIerrno\fP to \fBEAGAIN\fP. The time is specified 41 | as an absolute time using the parameter \fItimeout\fP. The behaviour is 42 | unspecified if \fItimeout\fP is \fINULL\fP. \fItimeout\fP is measured 43 | with the clock whose ID is specified by the \fIclockid\fP parameter. This 44 | clock must be a predicitable clock. 45 | .SH RETURN VALUES 46 | Upon successful completion, the functions 47 | .BR bus_poll_start () 48 | and 49 | .BR bus_poll_stop () 50 | returns 0. Otherwise the functions returns -1 and sets \fIerrno\fP to 51 | indicate the error. 52 | .PP 53 | Upon successful completion, the functions 54 | .BR bus_poll () 55 | and 56 | .BR bus_poll_timed () 57 | returns the received message. Otherwise the function returns \fINULL\fP 58 | and sets \fIerrno\fP to indicate the error. 59 | .SH ERRORS 60 | The 61 | .BR bus_poll (3), 62 | .BR bus_poll_start (3) 63 | and 64 | .BR bus_poll_stop (3) 65 | functions may fail and set \fIerrno\fP to any of the errors specified for 66 | .BR semop (3). 67 | The 68 | .BR bus_poll_timed (3) 69 | function may also set \fIerrno\fP to any of the errors specified for 70 | .BR clock_gettime (3). 71 | .SH SEE ALSO 72 | .BR bus-create (1), 73 | .BR bus (5), 74 | .BR libbus (7), 75 | .BR bus_open (3), 76 | .BR bus_write (3), 77 | .BR bus_read (3), 78 | .BR semop (3), 79 | .BR clock_gettime (3) 80 | -------------------------------------------------------------------------------- /bus_read.3: -------------------------------------------------------------------------------- 1 | .TH BUS_READ 3 BUS 2 | .SH NAME 3 | bus_read, bus_read_timed - Listen for new messages a bus 4 | .SH SYNOPSIS 5 | .LP 6 | .nf 7 | #include 8 | .P 9 | int bus_read(const bus_t *\fIbus\fP, int (*\fIcallback\fP)(const char *\fImessage\fP, void *\fIuser_data\fP), 10 | void *\fIuser_data\fP); 11 | int bus_read_timed(const bus_t *\fIbus\fP, int (*\fIcallback\fP)(const char *\fImessage\fP, void *\fIuser_data\fP), 12 | void *\fIuser_data\fP, const struct timespec *\fItimeout\fP, clockid_t \fIclockid\fP); 13 | .fi 14 | .SH DESCRIPTION 15 | The 16 | .BR bus_read () 17 | function waits for new message to be sent on the bus specified in 18 | \fIbus\fP, as provieded by a previous call to the 19 | .BR bus_open () 20 | function. Once a message is received, the \fIcallback\fP function is 21 | invoked. The \fImessage\fP argument to the callback is the received 22 | message, and \fIuser_data\fP for \fIcallback\fP should be 23 | \fIuser_data\fP from 24 | .BR bus_read (). 25 | However, once 26 | .BR bus_read () 27 | has ensured that it will receive any message sent on the bus, it shall 28 | invoke the \fIcallback\fP function with \fImessage\fP set to \fINULL\fP, 29 | to notify the process that it can perform any action that requires that 30 | it is listening on the bus. 31 | .PP 32 | After \fIcallback\fP returns, \fImessage\fP may be override. Therefore 33 | \fIcallback\fP should copy \fImessage\fP and start a new thread that 34 | uses the copy of \fImessage\fP. \fIcallback\fP shall return -1 on 35 | failure, 0 if 36 | .BR bus_read () 37 | should stop listening or 1 if 38 | .BR bus_read () 39 | should continue listening. 40 | .PP 41 | The 42 | .BR bus_read_timed () 43 | function behaves like 44 | .BR bus_read (), 45 | except it will automatically fail and set \fIerrno\fP to \fBEAGAIN\fP 46 | when the specified time has passed. The time is specified as an 47 | absolute time using the parameter \fItimeout\fP. The behaviour is 48 | unspecified if \fItimeout\fP is \fINULL\fP. \fItimeout\fP is measured 49 | with the clock whose ID is specified by the \fIclockid\fP parameter. 50 | This clock must be a predicitable clock. 51 | .SH RETURN VALUES 52 | Upon successful completion, these functions returns 0. Otherwise the 53 | function returns -1 and sets \fIerrno\fP to indicate the error. 54 | .SH ERRORS 55 | The 56 | .BR bus_read (3) 57 | function may fail and set \fIerrno\fP to any of the errors specified for 58 | .BR semop (3). 59 | The 60 | .BR bus_read_timed (3) 61 | function may also set \fIerrno\fP to any of the errors specified for 62 | .BR clock_gettime (3). 63 | .SH SEE ALSO 64 | .BR bus-create (1), 65 | .BR bus (5), 66 | .BR libbus (7), 67 | .BR bus_open (3), 68 | .BR bus_write (3), 69 | .BR bus_poll (3), 70 | .BR semop (3), 71 | .BR clock_gettime (3) 72 | -------------------------------------------------------------------------------- /bus_unlink.3: -------------------------------------------------------------------------------- 1 | .TH BUS_UNLINK 3 BUS 2 | .SH NAME 3 | bus_unlink - Remove a bus 4 | .SH SYNOPSIS 5 | .LP 6 | .nf 7 | #include 8 | .P 9 | int bus_unlink(const char *\fIfile\fP); 10 | .fi 11 | .SH DESCRIPTION 12 | The 13 | .BR bus_unlink () 14 | function removes the bus assoicated with the pathname stored in 15 | \fIfile\fP. The function also unlinks the file. 16 | .SH RETURN VALUES 17 | Upon successful completion, the function returns 0. Otherwise the 18 | function returns -1 and sets \fIerrno\fP to indicate the error. 19 | .SH ERRORS 20 | .TP 21 | .B EINVAL 22 | The bus does not exist. 23 | .TP 24 | .B EACCES 25 | Operation permission is denied to the calling process. 26 | .TP 27 | .B EPERM 28 | The user does not have permission to remove the bus. 29 | .PP 30 | The 31 | .BR bus_unlink (3) 32 | function may also fail and set \fIerrno\fP to any of the errors 33 | specified for the routines 34 | .BR unlink (2), 35 | .BR open (2), 36 | .BR semget (3) 37 | and 38 | .BR shmget (3). 39 | .SH SEE ALSO 40 | .BR bus-create (1), 41 | .BR bus (5), 42 | .BR libbus (7), 43 | .BR bus_create (3), 44 | .BR bus_close (3), 45 | .BR unlink (2), 46 | .BR open (2), 47 | .BR semget (3), 48 | .BR shmget (3) 49 | -------------------------------------------------------------------------------- /bus_write.3: -------------------------------------------------------------------------------- 1 | .TH BUS_WRITE 3 BUS 2 | .SH NAME 3 | bus_write, bus_write_timed - Broadcast a message a bus 4 | .SH SYNOPSIS 5 | .LP 6 | .nf 7 | #include 8 | .P 9 | int bus_write(const bus_t *\fIbus\fP, const char *\fImessage\fP, int \fIflags\fP); 10 | int bus_write_timed(const bus_t *\fIbus\fP, const char *\fImessage\fP, 11 | const struct timespec *\fItimeout\fP, clockid_t \fIclockid\fP); 12 | .fi 13 | .SH DESCRIPTION 14 | The 15 | .BR bus_write () 16 | function broadcasts a message on the bus whose information is stored in 17 | \fIbus\fP. The message read by the function is stored in the parameter 18 | \fImessage\fP. It may not exceeed 2048 bytes, including NULL termination. 19 | .PP 20 | The 21 | .BR bus_write () 22 | function shall fail, and set \fIerrno\fP to \fIEAGAIN\fP, if the call 23 | would suspend the process and (\fIflags\fP &BUS_NOWAIT). 24 | .PP 25 | The 26 | .BR bus_write_timed () 27 | function behaves like 28 | .BR bus_write (), 29 | except if it is not able to write the \fImessage\fP within the specified 30 | time, it will fail and set \fIerrno\fP to \fBEAGAIN\fP. The time is 31 | specified as an absolute time using the parameter \fItimeout\fP. The 32 | behaviour is unspecified if \fItimeout\fP is \fINULL\fP. \fItimeout\fP 33 | is measured with the clock whose ID is specified by the \fIclockid\fP 34 | parameter. This clock must be a predicitable clock. 35 | .SH RETURN VALUES 36 | Upon successful completion, these functions returns 0. Otherwise the 37 | function returns -1 and sets \fIerrno\fP to indicate the error. 38 | .SH ERRORS 39 | The 40 | .BR bus_write (3) 41 | function may fail and set \fIerrno\fP to any of the errors specified for 42 | .BR semop (3). 43 | The 44 | .BR bus_write_timed (3) 45 | function may also set \fIerrno\fP to any of the errors specified for 46 | .BR clock_gettime (3). 47 | .SH SEE ALSO 48 | .BR bus-create (1), 49 | .BR bus (5), 50 | .BR libbus (7), 51 | .BR bus_open (3), 52 | .BR bus_read (3), 53 | .BR bus_poll (3), 54 | .BR bus_chown (3), 55 | .BR bus_chmod (3), 56 | .BR clock_gettime (3) 57 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | PREFIX = /usr/local 2 | MANPREFIX = $(PREFIX)/share/man 3 | 4 | CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE 5 | CFLAGS = -std=c99 -Wall -Wextra -pedantic -O2 $(CPPFLAGS) 6 | LDFLAGS = -s -lrt 7 | 8 | # Add -DSEMUN_ALREADY_DEFINED to CPPFLAGS if `union semun` is already defined by libc 9 | -------------------------------------------------------------------------------- /doc/examples/audio-volume-control/README: -------------------------------------------------------------------------------- 1 | Use-case example. 2 | 3 | Assume you have program that display the audio volume. 4 | This program checks every second third if the volume 5 | have changed. 6 | 7 | Also assume that you use amixer to change the volume, 8 | most often by using keybindings via xbindkeys. 9 | 10 | To reduce the delay, you want to send a signal to the 11 | monitor program that the volume have changed. For this 12 | more primitive IPC is sufficient, but lets assume there 13 | are other programs interested in this information too. 14 | 15 | To accomplish this, you create a wrapper for amixer 16 | that broadcasts updates on a bus. This wrapper is 17 | installed as ~/.local/bin/amixer, and ~/.local/bin/ 18 | is included in $PATH before /usr/bin. 19 | 20 | 21 | 22 | Before starting run ./init, this code is 23 | should be run from your profile file if you 24 | want to implement this on your system. 25 | 26 | After running ./init, you can start one 27 | or more listeners by running ./alsa-monitor. 28 | 29 | To change the volume run 30 | `./amixer -c 0 -- set Master 5%+` or similar. 31 | 32 | When you are done run ./cleanup 33 | 34 | -------------------------------------------------------------------------------- /doc/examples/audio-volume-control/amixer: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /usr/bin/amixer "$@" 3 | for arg in "$@"; do 4 | if [ "${arg}" = "set" ] || [ "${arg}" = "sset" ] || [ "${arg}" = "cset" ]; then 5 | exec bus broadcast "/tmp/example-bus" '0 volume-changed *' 6 | fi 7 | done 8 | 9 | -------------------------------------------------------------------------------- /doc/examples/audio-volume-control/cleanup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec bus remove "/tmp/example-bus" 3 | 4 | -------------------------------------------------------------------------------- /doc/examples/audio-volume-control/init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | bus create "/tmp/example-bus" 3 | 4 | # The following code is more suitable in the real world, 5 | # if used, the other files should use "${BUS_AUDIO}" 6 | # instead of "/tmp/example-bus". 7 | # 8 | # export BUS_AUDIO="${XDG_RUNTIME_DIR}/bus/audio" 9 | # if [ ! -f "${BUS_AUDIO}" ]; then 10 | # bus create "${BUS_AUDIO}" 11 | # fi 12 | 13 | -------------------------------------------------------------------------------- /doc/examples/audio-volume-control/monitor: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ $# = 1 ]; then 3 | if [ "$(echo "${1}" | cut -d ' ' -f 2)" = "volume-changed" ]; then 4 | printf '\e[H\e[2J' 5 | amixer get Master 6 | fi 7 | exit 0 8 | fi 9 | 10 | exec 2>/dev/null 11 | 12 | printf '\e[?1049h\e[H\e[2J' 13 | trap -- "printf '\e[?1049l'" SIGINT 14 | bus listen "/tmp/example-bus" \'"${0/\'/\'\\\'\'}"\'' "${msg}"' 15 | 16 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/.gitignore: -------------------------------------------------------------------------------- 1 | run/ 2 | announce 3 | await-ready 4 | await-started 5 | cleanup 6 | init 7 | require 8 | start-daemon 9 | test-daemon 10 | 11 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/Makefile: -------------------------------------------------------------------------------- 1 | COMMANDS = announce await-ready await-started cleanup init require start-daemon test-daemon 2 | 3 | all: ${COMMANDS} 4 | 5 | %: %.c 6 | ${CC} -Wall -Wextra -pedantic -std=c99 -lbus -o $@ $< 7 | 8 | clean: 9 | -rm ${COMMANDS} 10 | -rm -r run 11 | 12 | 13 | .PHONY: all clean 14 | 15 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/README: -------------------------------------------------------------------------------- 1 | Use-case example. 2 | 3 | This example shows how bus can be used in an init 4 | system to provide "aggressivly" parallel startup 5 | of daemons. 6 | 7 | 8 | First of, run make to build this example. 9 | 10 | To start the example run ./init. It will print in 11 | red export-statement you may want to run i other 12 | terminals. You will need to select at least one 13 | daemon, for example you can run `./init d-ntp`. 14 | The available pretend daemons are: d-network, 15 | d-ntp and d-ssh. 16 | 17 | When you are done run ./cleanup with BUS_INIT 18 | exported with the value printed by ./init. 19 | 20 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/announce.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define t(stmt) if (stmt) goto fail 8 | 9 | 10 | static char arg[4098]; 11 | 12 | 13 | int 14 | main(int argc, char *argv[]) 15 | { 16 | bus_t bus; 17 | if (argc < 3) 18 | return fprintf(stderr, "USAGE: %s state daemon", *argv), 2; 19 | t(bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 20 | sprintf(arg, "%ji %s %s", (intmax_t)getppid(), argv[1], argv[2]); 21 | t(bus_write(&bus, arg, 0)); 22 | t(bus_close(&bus)); 23 | return 0; 24 | 25 | fail: 26 | perror("announce"); 27 | return 1; 28 | } 29 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/await-ready.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define t(stmt) if (stmt) goto fail 10 | 11 | 12 | static char arg[4098]; 13 | static int argc; 14 | static char **argv; 15 | static int remaining = 0; 16 | static char *started = NULL; 17 | static char msg[BUS_MEMORY_SIZE]; 18 | 19 | 20 | static void 21 | announce_wait(pid_t pid) 22 | { 23 | bus_t bus; 24 | int i; 25 | t(bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 26 | for (i = 1; i < argc; i++) { 27 | if (!started[i]) { 28 | sprintf(arg, "%ji awaiting-ready %s", (intmax_t)pid, argv[i]); 29 | t(bus_write(&bus, arg, 0)); 30 | } 31 | } 32 | t(bus_close(&bus)); 33 | return; 34 | 35 | fail: 36 | perror("await-ready"); 37 | } 38 | 39 | 40 | static int 41 | callback(const char *message, void *user_data) 42 | { 43 | int i; 44 | char *arg2; 45 | char *arg3; 46 | pid_t pid; 47 | pid_t ppid; 48 | 49 | if (!message) { 50 | ppid = getppid(); 51 | pid = fork(); 52 | if (pid == 0) { 53 | if (fork() == 0) 54 | announce_wait(ppid); 55 | exit(0); 56 | } else { 57 | (void) waitpid(pid, NULL, 0); /* Let's pretend everything will go swimmingly. */ 58 | } 59 | return 1; 60 | } 61 | 62 | strncpy(msg, message, BUS_MEMORY_SIZE - 1); 63 | msg[BUS_MEMORY_SIZE - 1] = 0; 64 | 65 | arg2 = strchr(msg, ' '); 66 | if (!arg2) 67 | return 1; 68 | arg3 = strchr(++arg2, ' '); 69 | if (!arg3) 70 | return 1; 71 | *arg3++ = 0; 72 | 73 | if (strcmp(arg2, "ready")) 74 | return 1; 75 | 76 | for (i = 1; i < argc; i++) 77 | if (!started[i] && !strcmp(argv[i], arg3)) 78 | started[i] = 1, remaining--; 79 | 80 | return !!remaining; 81 | (void) user_data; 82 | } 83 | 84 | 85 | int 86 | main(int argc_, char *argv_[]) 87 | { 88 | bus_t bus; 89 | int i; 90 | 91 | argc = argc_; 92 | argv = argv_; 93 | 94 | if (argc < 2) 95 | return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; 96 | t(bus_open(&bus, getenv("BUS_INIT"), BUS_RDONLY)); 97 | started = calloc(argc, sizeof(char)); 98 | t(started == NULL); 99 | 100 | started[0] = 1; 101 | for (i = 1; i < argc; i++) { 102 | sprintf(arg, "grep '^%s$' < \"${XDG_RUNTIME_DIR}/ready-daemons\" >/dev/null", argv[i]); 103 | if (!WEXITSTATUS(system(arg))) 104 | started[i] = 1; 105 | else 106 | remaining++; 107 | } 108 | 109 | if (remaining) 110 | bus_read(&bus, callback, NULL); 111 | 112 | bus_close(&bus); 113 | free(started); 114 | return 0; 115 | 116 | fail: 117 | perror("await-ready"); 118 | bus_close(&bus); 119 | free(started); 120 | return 1; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/await-started.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define t(stmt) if (stmt) goto fail 10 | 11 | 12 | static char arg[4098]; 13 | static int argc; 14 | static char **argv; 15 | static int remaining = 0; 16 | static char *started = NULL; 17 | static char msg[BUS_MEMORY_SIZE]; 18 | 19 | 20 | static void 21 | announce_wait(pid_t pid) 22 | { 23 | bus_t bus; 24 | int i; 25 | t(bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 26 | for (i = 1; i < argc; i++) { 27 | if (!started[i]) { 28 | sprintf(arg, "%ji awaiting-started %s", (intmax_t)pid, argv[i]); 29 | t(bus_write(&bus, arg, 0)); 30 | } 31 | } 32 | t(bus_close(&bus)); 33 | return; 34 | 35 | fail: 36 | perror("await-started"); 37 | } 38 | 39 | 40 | static int 41 | callback(const char *message, void *user_data) 42 | { 43 | int i; 44 | char *arg2; 45 | char *arg3; 46 | pid_t pid; 47 | pid_t ppid; 48 | 49 | if (!message) { 50 | ppid = getppid(); 51 | pid = fork(); 52 | if (pid == 0) { 53 | if (fork() == 0) 54 | announce_wait(ppid); 55 | exit(0); 56 | } else { 57 | (void) waitpid(pid, NULL, 0); /* Let's pretend everything will go swimmingly. */ 58 | } 59 | return 1; 60 | } 61 | 62 | strncpy(msg, message, BUS_MEMORY_SIZE - 1); 63 | msg[BUS_MEMORY_SIZE - 1] = 0; 64 | 65 | arg2 = strchr(msg, ' '); 66 | if (!arg2) 67 | return 1; 68 | arg3 = strchr(++arg2, ' '); 69 | if (!arg3) 70 | return 1; 71 | *arg3++ = 0; 72 | 73 | if (strcmp(arg2, "started") && strcmp(arg2, "ready")) 74 | return 1; 75 | 76 | for (i = 1; i < argc; i++) 77 | if (!started[i] && !strcmp(argv[i], arg3)) 78 | started[i] = 1, remaining--; 79 | 80 | return !!remaining; 81 | (void) user_data; 82 | } 83 | 84 | 85 | int 86 | main(int argc_, char *argv_[]) 87 | { 88 | bus_t bus; 89 | int i; 90 | 91 | argc = argc_; 92 | argv = argv_; 93 | 94 | if (argc < 2) 95 | return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; 96 | t(bus_open(&bus, getenv("BUS_INIT"), BUS_RDONLY)); 97 | started = calloc(argc, sizeof(char)); 98 | t(started == NULL); 99 | 100 | started[0] = 1; 101 | for (i = 1; i < argc; i++) { 102 | sprintf(arg, "grep '^%s$' < \"${XDG_RUNTIME_DIR}/started-daemons\" >/dev/null", argv[i]); 103 | if (!WEXITSTATUS(system(arg))) { 104 | started[i] = 1; 105 | } else { 106 | sprintf(arg, "grep '^%s$' < \"${XDG_RUNTIME_DIR}/ready-daemons\" >/dev/null", argv[i]); 107 | if (!WEXITSTATUS(system(arg))) 108 | started[i] = 1; 109 | else 110 | remaining++; 111 | } 112 | } 113 | 114 | if (remaining) 115 | bus_read(&bus, callback, NULL); 116 | 117 | bus_close(&bus); 118 | free(started); 119 | return 0; 120 | 121 | fail: 122 | perror("await-started"); 123 | bus_close(&bus); 124 | free(started); 125 | return 1; 126 | } 127 | 128 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/cleanup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define t(stmt) if (stmt) goto fail 6 | 7 | 8 | 9 | int 10 | main() 11 | { 12 | char *bus_address = getenv("BUS_INIT"); 13 | if (!bus_address || !*bus_address) { 14 | fprintf(stderr, "$BUS_INIT has not been set, its export statement " 15 | "should have been printed in bold red by ./init\n"); 16 | return 1; 17 | } 18 | t(bus_unlink(bus_address)); 19 | return 0; 20 | 21 | fail: 22 | perror("cleanup"); 23 | return 1; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/d-network: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PATH=.:$PATH 3 | d=d-network 4 | 5 | echo $d: starting 6 | sleep 2 7 | echo $d: ready 8 | announce ready $d 9 | 10 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/d-ntp: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PATH=.:$PATH 3 | d=d-ntp 4 | 5 | require d-network 6 | echo $d: started 7 | announce started $d 8 | await-ready d-network 9 | echo $d: ready 10 | announce ready $d 11 | 12 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/d-ssh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PATH=.:$PATH 3 | d=d-ssh 4 | 5 | require d-network 6 | echo $d: starting 7 | sleep 1 8 | echo $d: started 9 | announce started $d 10 | sleep 1 11 | echo $d: ready 12 | announce ready $d 13 | 14 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define t(stmt) if (stmt) goto fail 9 | 10 | 11 | static char msg[BUS_MEMORY_SIZE]; 12 | static int argc; 13 | static char **argv; 14 | static char arg[4098]; 15 | 16 | 17 | static void 18 | start_daemons() 19 | { 20 | int i; 21 | for (i = 1; i < argc; i++) 22 | if (fork() == 0) 23 | execl("./start-daemon", "./start-daemon", argv[i], NULL); 24 | } 25 | 26 | 27 | static int 28 | callback(const char *message, void *user_data) 29 | { 30 | pid_t pid; 31 | char *arg2; 32 | char *arg3; 33 | if (!message) { 34 | pid = fork(); 35 | t(pid == -1); 36 | if (pid == 0) { 37 | if (fork() == 0) { 38 | start_daemons(); 39 | } 40 | exit(0); 41 | } else { 42 | (void) waitpid(pid, NULL, 0); /* Let's pretend everything will go swimmingly. */ 43 | } 44 | return 1; 45 | } 46 | 47 | strncpy(msg, message, BUS_MEMORY_SIZE - 1); 48 | msg[BUS_MEMORY_SIZE - 1] = 0; 49 | 50 | pid = fork(); 51 | t(pid == -1); 52 | 53 | if (pid == 0) { 54 | if (fork() == 0) { 55 | arg2 = strchr(msg, ' '); 56 | if (arg2 == NULL) 57 | exit(0); 58 | arg3 = strchr(++arg2, ' '); 59 | if (arg3 == NULL) 60 | exit(0); 61 | *arg3++ = 0; 62 | if (!strcmp(arg2, "require")) { 63 | execl("./start-daemon", "./start-daemon", arg3, NULL); 64 | } else if (!strcmp(arg2, "awaiting-started")) { 65 | execl("./test-daemon", "./test-daemon", arg3, "started", NULL); 66 | } else if (!strcmp(arg2, "awaiting-ready") || !strcmp(arg2, "awaiting")) { 67 | execl("./test-daemon", "./test-daemon", arg3, "ready", NULL); 68 | } else if (!strcmp(arg2, "started")) { 69 | sprintf(arg, 70 | "grep '^%s\\$' < \"${XDG_RUNTIME_DIR}/started-daemons\" >/dev/null || " 71 | "echo %s >> \"${XDG_RUNTIME_DIR}/started-daemons\"", 72 | arg3, arg3); 73 | execlp("sh", "sh", "-c", arg, NULL); 74 | } else if (!strcmp(arg2, "ready")) { 75 | sprintf(arg, 76 | "grep '^%s\\$' < \"${XDG_RUNTIME_DIR}/ready-daemons\" >/dev/null || " 77 | "echo %s >> \"${XDG_RUNTIME_DIR}/ready-daemons\"", 78 | arg3, arg3); 79 | execlp("sh", "sh", "-c", arg, NULL); 80 | } 81 | } 82 | exit(0); 83 | } else { 84 | (void) waitpid(pid, NULL, 0); /* Let's pretend everything will go swimmingly. */ 85 | } 86 | 87 | return 1; 88 | (void) user_data; 89 | 90 | fail: 91 | perror("init"); 92 | return -1; 93 | } 94 | 95 | 96 | int 97 | main(int argc_, char *argv_[]) 98 | { 99 | char *bus_address = NULL; 100 | bus_t bus; 101 | argv = argv_; 102 | argc = argc_; 103 | if (argc < 2) { 104 | fprintf(stderr, "USAGE: %s daemon...\n", *argv); 105 | return 1; 106 | } 107 | t(setenv("XDG_RUNTIME_DIR", "./run", 1)); /* Real init systems with not have the period. */ 108 | system("mkdir -p -- \"${XDG_RUNTIME_DIR}\""); 109 | system("truncate -s 0 -- \"${XDG_RUNTIME_DIR}/started-daemons\""); 110 | system("truncate -s 0 -- \"${XDG_RUNTIME_DIR}/ready-daemons\""); 111 | t(bus_create(NULL, 1, &bus_address)); 112 | fprintf(stderr, "\033[00;01;31mexport BUS_INIT=%s\033[00m\n", bus_address); 113 | fprintf(stderr, "\033[00;31mexport XDG_RUNTIME_DIR=./run\033[00m\n"); 114 | t(setenv("BUS_INIT", bus_address, 1)); 115 | t(bus_open(&bus, bus_address, BUS_RDONLY)); 116 | t(bus_read(&bus, callback, NULL)); 117 | bus_close(&bus); 118 | free(bus_address); 119 | return 0; 120 | 121 | fail: 122 | perror("init"); 123 | bus_close(&bus); 124 | free(bus_address); 125 | return 1; 126 | } 127 | 128 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/require.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define t(stmt) if (stmt) goto fail 8 | 9 | 10 | static char arg[4098]; 11 | 12 | 13 | int 14 | main(int argc, char *argv[]) 15 | { 16 | bus_t bus; 17 | int i; 18 | if (argc < 2) 19 | return fprintf(stderr, "USAGE: %s daemon...", *argv), 2; 20 | t(bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 21 | 22 | for (i = 1; i < argc; i++) { 23 | sprintf(arg, "grep '^%s$' < \"${XDG_RUNTIME_DIR}/started-daemons\" >/dev/null", argv[i]); 24 | if (WEXITSTATUS(system(arg))) { 25 | sprintf(arg, "%ji require %s", (intmax_t)getppid(), argv[i]); 26 | t(bus_write(&bus, arg, 0)); 27 | } 28 | } 29 | 30 | bus_close(&bus); 31 | return 0; 32 | 33 | fail: 34 | perror("require"); 35 | bus_close(&bus); 36 | return 1; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/start-daemon.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define t(stmt) if (stmt) goto fail 9 | 10 | 11 | static char arg[4098]; 12 | 13 | 14 | int 15 | main(int argc, char *argv[]) 16 | { 17 | if (argc != 2) 18 | return fprintf(stderr, "This program should be called from ./init\n"), 2; 19 | 20 | sprintf(arg, "grep '^%s$' < \"${XDG_RUNTIME_DIR}/started-daemons\" >/dev/null", argv[1]); 21 | if (!WEXITSTATUS(system(arg))) 22 | return 0; 23 | sprintf(arg, "grep '^%s$' < \"${XDG_RUNTIME_DIR}/ready-daemons\" >/dev/null", argv[1]); 24 | if (!WEXITSTATUS(system(arg))) 25 | return 0; 26 | 27 | sprintf(arg, "./%s", argv[1]); 28 | execlp(arg, arg, NULL); 29 | perror("start-daemon"); 30 | return 1; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /doc/examples/daemon-dependencies/test-daemon.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define t(stmt) if (stmt) goto fail 7 | 8 | 9 | static char arg[4098]; 10 | 11 | 12 | int 13 | main(int argc, char *argv[]) 14 | { 15 | bus_t bus; 16 | if (argc != 3) 17 | return fprintf(stderr, "This program should be called from ./init\n"), 2; 18 | retry: 19 | sprintf(arg, "grep '^%s$' < \"${XDG_RUNTIME_DIR}/%s-daemons\" >/dev/null", argv[1], argv[2]); 20 | if (!WEXITSTATUS(system(arg))) { 21 | t(bus_open(&bus, getenv("BUS_INIT"), BUS_WRONLY)); 22 | sprintf(arg, "0 %s %s", argv[2], argv[1]); 23 | t(bus_write(&bus, arg, 0)); 24 | bus_close(&bus); 25 | } else if (!strcmp(argv[2], "started")) { 26 | argv[2] = "ready"; 27 | goto retry; 28 | } 29 | return 0; 30 | 31 | fail: 32 | perror("test-daemon"); 33 | return 1; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /doc/examples/nonblocking/.gitignore: -------------------------------------------------------------------------------- 1 | cleanup 2 | init 3 | write 4 | poll 5 | 6 | -------------------------------------------------------------------------------- /doc/examples/nonblocking/Makefile: -------------------------------------------------------------------------------- 1 | COMMANDS = init cleanup write poll 2 | 3 | all: ${COMMANDS} 4 | 5 | %: %.c 6 | ${CC} -Wall -Wextra -pedantic -std=c99 -lbus -o $@ $< 7 | 8 | clean: 9 | -rm ${COMMANDS} 10 | 11 | 12 | .PHONY: all clean 13 | 14 | -------------------------------------------------------------------------------- /doc/examples/nonblocking/README: -------------------------------------------------------------------------------- 1 | API usage example 2 | 3 | This example shows how to use bus_poll instead of bus_read, 4 | and how to do non-blocking polling and non-blocking writing. 5 | 6 | 7 | 8 | First of, run make to build this example. 9 | 10 | To start the example run ./init. 11 | When you are done run ./cleanup. 12 | 13 | Running instances of ./poll will check every second 14 | if there is a new inbound message. Between these checks 15 | ./write will wait for all ./poll:s to receive the message. 16 | This means that ./write blocks while ./poll sleeps. 17 | If two or more instances of ./write is started at 18 | approximately the same time, only one will continue to 19 | write a message on the bus, the others will fail. 20 | 21 | ./poll will stop if the message "stop" is broadcasted. 22 | 23 | ./poll, ./init and ./cleanup are run without any 24 | additional arguments. ./write is run with the message 25 | as the second argument. 26 | 27 | -------------------------------------------------------------------------------- /doc/examples/nonblocking/cleanup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int 5 | main() 6 | { 7 | return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /doc/examples/nonblocking/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int 5 | main() 6 | { 7 | return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /doc/examples/nonblocking/poll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define t(stmt) if (stmt) goto fail 8 | 9 | 10 | 11 | int 12 | main() 13 | { 14 | bus_t bus; 15 | const char *message; 16 | long long tick = 0; 17 | t(bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 18 | t(bus_poll_start(&bus)); 19 | for (;;) { 20 | message = bus_poll(&bus, BUS_NOWAIT); 21 | if (message == NULL) { 22 | t(errno != EAGAIN); 23 | printf("waiting... %lli\n", ++tick); 24 | sleep(1); 25 | continue; 26 | } 27 | tick = 0; 28 | message = strchr(message, ' ') + 1; 29 | if (!strcmp(message, "stop")) 30 | break; 31 | printf("\033[01m%s\033[21m\n", message); 32 | } 33 | t(bus_poll_stop(&bus)); 34 | bus_close(&bus); 35 | return 0; 36 | 37 | fail: 38 | perror("poll"); 39 | bus_poll_stop(&bus); 40 | bus_close(&bus); 41 | return 1; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /doc/examples/nonblocking/write.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define t(stmt) if (stmt) goto fail 7 | 8 | 9 | 10 | static char message[BUS_MEMORY_SIZE]; 11 | 12 | 13 | 14 | int 15 | main(int argc, char *argv[]) 16 | { 17 | bus_t bus; 18 | if (argc < 2) { 19 | fprintf(stderr, "%s: USAGE: %s message\n", argv[0], argv[0]); 20 | return 2; 21 | } 22 | sprintf(message, "0 %s", argv[1]); 23 | t(bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); 24 | t(bus_write(&bus, message, BUS_NOWAIT)); 25 | bus_close(&bus); 26 | return 0; 27 | 28 | fail: 29 | perror("write"); 30 | bus_close(&bus); 31 | return 1; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /doc/examples/telephony-and-music/.gitignore: -------------------------------------------------------------------------------- 1 | cleanup 2 | init 3 | monitor 4 | end-call 5 | receive-or-make-call 6 | 7 | -------------------------------------------------------------------------------- /doc/examples/telephony-and-music/Makefile: -------------------------------------------------------------------------------- 1 | COMMANDS = init cleanup monitor end-call receive-or-make-call 2 | 3 | all: ${COMMANDS} 4 | 5 | %: %.c 6 | ${CC} -Wall -Wextra -pedantic -std=c99 -lbus -o $@ $< 7 | 8 | clean: 9 | -rm ${COMMANDS} 10 | 11 | 12 | .PHONY: all clean 13 | 14 | -------------------------------------------------------------------------------- /doc/examples/telephony-and-music/README: -------------------------------------------------------------------------------- 1 | Use-case example. 2 | 3 | Assume you have a music player and a telephony program. 4 | You might like it if the music player pauses whenever 5 | you make or receive a call. You may also like it, if 6 | the music resumed when the call ended. 7 | 8 | In this example we will assume you the have moc/mocp 9 | running. And we will use the shell to simulate a 10 | telephony program. 11 | 12 | 13 | 14 | First of, run make to build this example. 15 | Before starting run ./init. 16 | And when you are done run ./cleanup. 17 | 18 | In one terminal run ./monitor. This program will 19 | pause mocp when you make or receive a call, it will 20 | also resume mocp when all calls have ended if it 21 | did pause mocp. 22 | 23 | Then start any positive number of terminals. 24 | We will pretend that each of them are telephony 25 | programs. To make or receive a call, run 26 | ./receive-or-make-call, when you want to end 27 | the pretend call, run ./end-call from the 28 | terminal (or more accurately, from the same 29 | process). 30 | 31 | -------------------------------------------------------------------------------- /doc/examples/telephony-and-music/cleanup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int 5 | main() 6 | { 7 | return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /doc/examples/telephony-and-music/end-call.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define t(stmt) if (stmt) goto fail 7 | 8 | 9 | 10 | static char message[BUS_MEMORY_SIZE]; 11 | 12 | 13 | 14 | int 15 | main() 16 | { 17 | bus_t bus; 18 | sprintf(message, "%ji unforce-pause", (intmax_t)getppid()); 19 | /* Yes, PPID; in this example we pretend the shell is the telephony process. */ 20 | t(bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); 21 | t(bus_write(&bus, message, 0)); 22 | bus_close(&bus); 23 | return 0; 24 | 25 | fail: 26 | perror("end-call"); 27 | bus_close(&bus); 28 | return 1; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /doc/examples/telephony-and-music/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int 5 | main() 6 | { 7 | return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /doc/examples/telephony-and-music/monitor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define t(stmt) if (stmt) goto fail 7 | 8 | 9 | 10 | static size_t pauser_count = 0; 11 | static size_t pausers_size = 0; 12 | static char* pausers = NULL; 13 | 14 | 15 | 16 | static int 17 | is_moc_playing(void) 18 | { 19 | return !WEXITSTATUS(system("env LANG=C mocp -i 2>/dev/null | grep 'State: PLAY' >/dev/null")); 20 | } 21 | 22 | 23 | 24 | /* In a proper implementation, message whould be copyied, and then 25 | * a new thread would be created that parsed the copy. But that is 26 | * too much for an example, especially since it would also require 27 | * a mutex to make sure two threads do not modify data at the same 28 | * time, causing chaos. */ 29 | static int 30 | callback(const char *message, void *user_data) 31 | { 32 | char *msg = NULL; 33 | size_t len = 0; 34 | if (message == 0) 35 | return 1; 36 | while ((len < 2047) && message[len]) 37 | len++; 38 | msg = malloc((len + 1) * sizeof(char)); 39 | t(msg == NULL); 40 | memcpy(msg, message, len * sizeof(char)); 41 | msg[len] = 0; 42 | /* BEGIN run as in a separate thread */ 43 | if (pauser_count || is_moc_playing()) { 44 | char *begin = strchr(msg, ' '); 45 | ssize_t pid; 46 | int requests_pause; 47 | if (begin == NULL) 48 | goto done; 49 | *begin++ = 0; 50 | pid = (ssize_t)atoll(msg); 51 | if (pid < 1) /* We need a real PID, too bad there is 52 | no convient way to detect if it dies. */ 53 | goto done; 54 | if ((strstr(begin, "force-pause ") == begin) || !strcmp(begin, "force-pause")) 55 | requests_pause = 1; 56 | else if ((strstr(begin, "unforce-pause ") == begin) || !strcmp(begin, "unforce-pause")) 57 | requests_pause = 0; 58 | else 59 | goto done; 60 | if ((size_t)pid >= pausers_size) { 61 | pausers = realloc(pausers, (size_t)(pid + 1) * sizeof(char)); 62 | t(pausers == NULL); /* Let's ignore the memory leak. */ 63 | memset(pausers + pausers_size, 0, ((size_t)(pid + 1) - pausers_size) * sizeof(char)); 64 | pausers_size = (size_t)(pid + 1); 65 | } 66 | if (pausers[pid] ^ requests_pause) { 67 | pauser_count += requests_pause ? 1 : -1; 68 | pausers[pid] = requests_pause; 69 | if (pauser_count == (size_t)requests_pause) 70 | system(requests_pause ? "mocp -P" : "mocp -U"); 71 | } 72 | } 73 | /* END run as in a separate thread */ 74 | goto done; 75 | (void) user_data; 76 | 77 | fail: 78 | perror("monitor"); 79 | return -1; 80 | 81 | done: 82 | free(msg); 83 | return 1; 84 | } 85 | 86 | 87 | 88 | int 89 | main() 90 | { 91 | bus_t bus; 92 | t(bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 93 | t(bus_read(&bus, callback, NULL)); 94 | bus_close(&bus); 95 | free(pausers); 96 | return 0; 97 | 98 | fail: 99 | perror("monitor"); 100 | bus_close(&bus); 101 | free(pausers); 102 | return 1; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /doc/examples/telephony-and-music/receive-or-make-call.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define t(stmt) if (stmt) goto fail 7 | 8 | 9 | 10 | static char message[BUS_MEMORY_SIZE]; 11 | 12 | 13 | 14 | int 15 | main() 16 | { 17 | bus_t bus; 18 | sprintf(message, "%ji force-pause", (intmax_t)getppid()); 19 | /* Yes, PPID; in this example we pretend the shell is the telephony process. */ 20 | t(bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); 21 | t(bus_write(&bus, message, 0)); 22 | bus_close(&bus); 23 | return 0; 24 | 25 | fail: 26 | perror("receive-or-make-call"); 27 | bus_close(&bus); 28 | return 1; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /doc/examples/timed/.gitignore: -------------------------------------------------------------------------------- 1 | cleanup 2 | init 3 | write 4 | poll 5 | read 6 | slow-poll 7 | 8 | -------------------------------------------------------------------------------- /doc/examples/timed/Makefile: -------------------------------------------------------------------------------- 1 | COMMANDS = init cleanup write poll read slow-poll 2 | 3 | all: ${COMMANDS} 4 | 5 | %: %.c 6 | ${CC} -Wall -Wextra -pedantic -std=c99 -lbus -o $@ $< 7 | 8 | clean: 9 | -rm ${COMMANDS} 10 | 11 | 12 | .PHONY: all clean 13 | 14 | -------------------------------------------------------------------------------- /doc/examples/timed/README: -------------------------------------------------------------------------------- 1 | API usage example 2 | 3 | This example shows how to use timed operations. 4 | 5 | 6 | 7 | First of, run make to build this example. 8 | 9 | To start the example run ./init. 10 | When you are done run ./cleanup. 11 | 12 | Running instances of ./poll will wait for new messages 13 | continuously, but with one second timeouts. 14 | 15 | ./slow-poll works like ./poll, except it will sleep 16 | for one second every time it receives a message. 17 | 18 | Running instances of ./read will read for ten seconds 19 | and then time out. 20 | 21 | ./poll, ./read, and ./slow-poll will stop if the message 22 | "stop" is broadcasted. 23 | 24 | ./write will wait for atmost a tenth of a seconds before 25 | failing. This means that if two instances of ./write is 26 | started at the same time one of them will fail if 27 | ./slow-poll is running. 28 | 29 | ./poll, ./read, ./init and ./cleanup are run without any 30 | additional arguments. ./write is run with the message 31 | as the second argument. 32 | 33 | -------------------------------------------------------------------------------- /doc/examples/timed/cleanup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int 5 | main() 6 | { 7 | return bus_unlink("/tmp/example-bus") && (perror("cleanup"), 1); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /doc/examples/timed/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int 5 | main() 6 | { 7 | return bus_create("/tmp/example-bus", 0, NULL) && (perror("init"), 1); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /doc/examples/timed/poll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define t(stmt) if (stmt) goto fail 8 | 9 | 10 | 11 | int 12 | main() 13 | { 14 | bus_t bus; 15 | const char *message; 16 | long long tick = 0; 17 | struct timespec timeout; 18 | t(bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 19 | t(bus_poll_start(&bus)); 20 | for (;;) { 21 | t(clock_gettime(CLOCK_MONOTONIC, &timeout)); 22 | timeout.tv_sec += 1; 23 | message = bus_poll_timed(&bus, &timeout, CLOCK_MONOTONIC); 24 | if (message == NULL) { 25 | t(errno != EAGAIN); 26 | printf("waiting... %lli\n", ++tick); 27 | continue; 28 | } 29 | tick = 0; 30 | message = strchr(message, ' ') + 1; 31 | if (!strcmp(message, "stop")) 32 | break; 33 | printf("\033[01m%s\033[21m\n", message); 34 | } 35 | t(bus_poll_stop(&bus)); 36 | bus_close(&bus); 37 | return 0; 38 | 39 | fail: 40 | perror("poll"); 41 | bus_poll_stop(&bus); 42 | bus_close(&bus); 43 | return 1; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /doc/examples/timed/read.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define t(stmt) if (stmt) goto fail 8 | 9 | 10 | 11 | static int 12 | callback(const char *message, void *user_data) 13 | { 14 | (void) user_data; 15 | 16 | if (message == NULL) 17 | return 1; 18 | 19 | message = strchr(message, ' ') + 1; 20 | if (!strcmp(message, "stop")) 21 | return 0; 22 | printf("%s\n", message); 23 | return 1; 24 | } 25 | 26 | 27 | int 28 | main() 29 | { 30 | bus_t bus; 31 | struct timespec timeout; 32 | t(bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 33 | t(clock_gettime(CLOCK_MONOTONIC, &timeout)); 34 | timeout.tv_sec += 10; 35 | t(bus_read_timed(&bus, callback, NULL, &timeout, CLOCK_MONOTONIC)); 36 | bus_close(&bus); 37 | return 0; 38 | 39 | fail: 40 | perror("poll"); 41 | bus_poll_stop(&bus); 42 | bus_close(&bus); 43 | return 1; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /doc/examples/timed/slow-poll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define t(stmt) if (stmt) goto fail 8 | 9 | 10 | 11 | int 12 | main() 13 | { 14 | bus_t bus; 15 | const char *message; 16 | long long tick = 0; 17 | struct timespec timeout; 18 | t(bus_open(&bus, "/tmp/example-bus", BUS_RDONLY)); 19 | t(bus_poll_start(&bus)); 20 | for (;;) { 21 | t(clock_gettime(CLOCK_MONOTONIC, &timeout)); 22 | timeout.tv_sec += 1; 23 | message = bus_poll_timed(&bus, &timeout, CLOCK_MONOTONIC); 24 | if (message == NULL) { 25 | t(errno != EAGAIN); 26 | printf("waiting... %lli\n", ++tick); 27 | continue; 28 | } 29 | tick = 0; 30 | message = strchr(message, ' ') + 1; 31 | if (!strcmp(message, "stop")) 32 | break; 33 | printf("\033[01m%s\033[21m\n", message); 34 | sleep(1); 35 | } 36 | t(bus_poll_stop(&bus)); 37 | bus_close(&bus); 38 | return 0; 39 | 40 | fail: 41 | perror("poll"); 42 | bus_poll_stop(&bus); 43 | bus_close(&bus); 44 | return 1; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /doc/examples/timed/write.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define t(stmt) if (stmt) goto fail 7 | 8 | 9 | 10 | static char message[BUS_MEMORY_SIZE]; 11 | 12 | 13 | 14 | int 15 | main(int argc, char *argv[]) 16 | { 17 | bus_t bus; 18 | struct timespec timeout; 19 | if (argc < 2) { 20 | fprintf(stderr, "%s: USAGE: %s message\n", argv[0], argv[0]); 21 | return 2; 22 | } 23 | sprintf(message, "0 %s", argv[1]); 24 | t(bus_open(&bus, "/tmp/example-bus", BUS_WRONLY)); 25 | t(clock_gettime(CLOCK_MONOTONIC, &timeout)); 26 | timeout.tv_nsec += 100000000L; 27 | t(bus_write_timed(&bus, message, &timeout, CLOCK_MONOTONIC)); 28 | bus_close(&bus); 29 | return 0; 30 | 31 | fail: 32 | perror("write"); 33 | bus_close(&bus); 34 | return 1; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /doc/protocol: -------------------------------------------------------------------------------- 1 | create: 2 | Select a filename. 3 | 4 | Create XSI semaphore array {S = 0, W = 0, X = 1, Q = 0 and N = 0} 5 | with random key. Store the semaphore array's key in decimal form 6 | on the first line in the selected file. 7 | 8 | Create XSI shared memory, with an allocation of 2048 bytes, with a 9 | random key. Store the shared memory's key in decimal form on the 10 | second line in the selected file. 11 | 12 | 13 | broadcast: 14 | with P(X): 15 | Z(W) 16 | Write NUL-terminate message to shared memory 17 | with V(N): -- (1) 18 | Q := 0 19 | Z(S) 20 | 21 | -- (1) may be omitted if semaphores are known that 22 | P(·), Z(·), V(·) cannot create a race condition 23 | with a processes running Z(·). 24 | 25 | 26 | listen: 27 | with V(S): 28 | forever: 29 | V(Q) 30 | Z(Q) 31 | Read NUL-terminated message from shared memory 32 | if breaking: 33 | break 34 | with V(W): 35 | with P(S): 36 | Z(S) 37 | Z(N) 38 | 39 | 40 | `V(a)` means that semaphore a is released. 41 | `P(a)` means that semaphore a is acquired. 42 | `Z(a)` means that the process waits for semaphore a to become 0. 43 | `with P(a)` that `P(a)` is done before the entering the scope, 44 | and `V(a)` is done when exiting the scope. It also means that 45 | these actions [P(a) and V(a)] are undone when the process exits, 46 | or if the call fails. 47 | `with V(a)` is to `V(a)` as `with P(a)` is to `P(a)`. 48 | -------------------------------------------------------------------------------- /fdl.texinfo: -------------------------------------------------------------------------------- 1 | @c The GNU Free Documentation License. 2 | @center Version 1.3, 3 November 2008 3 | 4 | @c This file is intended to be included within another document, 5 | @c hence no sectioning command or @node. 6 | 7 | @display 8 | Copyright @copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. 9 | @uref{http://fsf.org/} 10 | 11 | Everyone is permitted to copy and distribute verbatim copies 12 | of this license document, but changing it is not allowed. 13 | @end display 14 | 15 | @enumerate 0 16 | @item 17 | PREAMBLE 18 | 19 | The purpose of this License is to make a manual, textbook, or other 20 | functional and useful document @dfn{free} in the sense of freedom: to 21 | assure everyone the effective freedom to copy and redistribute it, 22 | with or without modifying it, either commercially or noncommercially. 23 | Secondarily, this License preserves for the author and publisher a way 24 | to get credit for their work, while not being considered responsible 25 | for modifications made by others. 26 | 27 | This License is a kind of ``copyleft'', which means that derivative 28 | works of the document must themselves be free in the same sense. It 29 | complements the GNU General Public License, which is a copyleft 30 | license designed for free software. 31 | 32 | We have designed this License in order to use it for manuals for free 33 | software, because free software needs free documentation: a free 34 | program should come with manuals providing the same freedoms that the 35 | software does. But this License is not limited to software manuals; 36 | it can be used for any textual work, regardless of subject matter or 37 | whether it is published as a printed book. We recommend this License 38 | principally for works whose purpose is instruction or reference. 39 | 40 | @item 41 | APPLICABILITY AND DEFINITIONS 42 | 43 | This License applies to any manual or other work, in any medium, that 44 | contains a notice placed by the copyright holder saying it can be 45 | distributed under the terms of this License. Such a notice grants a 46 | world-wide, royalty-free license, unlimited in duration, to use that 47 | work under the conditions stated herein. The ``Document'', below, 48 | refers to any such manual or work. Any member of the public is a 49 | licensee, and is addressed as ``you''. You accept the license if you 50 | copy, modify or distribute the work in a way requiring permission 51 | under copyright law. 52 | 53 | A ``Modified Version'' of the Document means any work containing the 54 | Document or a portion of it, either copied verbatim, or with 55 | modifications and/or translated into another language. 56 | 57 | A ``Secondary Section'' is a named appendix or a front-matter section 58 | of the Document that deals exclusively with the relationship of the 59 | publishers or authors of the Document to the Document's overall 60 | subject (or to related matters) and contains nothing that could fall 61 | directly within that overall subject. (Thus, if the Document is in 62 | part a textbook of mathematics, a Secondary Section may not explain 63 | any mathematics.) The relationship could be a matter of historical 64 | connection with the subject or with related matters, or of legal, 65 | commercial, philosophical, ethical or political position regarding 66 | them. 67 | 68 | The ``Invariant Sections'' are certain Secondary Sections whose titles 69 | are designated, as being those of Invariant Sections, in the notice 70 | that says that the Document is released under this License. If a 71 | section does not fit the above definition of Secondary then it is not 72 | allowed to be designated as Invariant. The Document may contain zero 73 | Invariant Sections. If the Document does not identify any Invariant 74 | Sections then there are none. 75 | 76 | The ``Cover Texts'' are certain short passages of text that are listed, 77 | as Front-Cover Texts or Back-Cover Texts, in the notice that says that 78 | the Document is released under this License. A Front-Cover Text may 79 | be at most 5 words, and a Back-Cover Text may be at most 25 words. 80 | 81 | A ``Transparent'' copy of the Document means a machine-readable copy, 82 | represented in a format whose specification is available to the 83 | general public, that is suitable for revising the document 84 | straightforwardly with generic text editors or (for images composed of 85 | pixels) generic paint programs or (for drawings) some widely available 86 | drawing editor, and that is suitable for input to text formatters or 87 | for automatic translation to a variety of formats suitable for input 88 | to text formatters. A copy made in an otherwise Transparent file 89 | format whose markup, or absence of markup, has been arranged to thwart 90 | or discourage subsequent modification by readers is not Transparent. 91 | An image format is not Transparent if used for any substantial amount 92 | of text. A copy that is not ``Transparent'' is called ``Opaque''. 93 | 94 | Examples of suitable formats for Transparent copies include plain 95 | ASCII without markup, Texinfo input format, La@TeX{} input 96 | format, SGML or XML using a publicly available 97 | DTD, and standard-conforming simple HTML, 98 | PostScript or PDF designed for human modification. Examples 99 | of transparent image formats include PNG, XCF and 100 | JPG. Opaque formats include proprietary formats that can be 101 | read and edited only by proprietary word processors, SGML or 102 | XML for which the DTD and/or processing tools are 103 | not generally available, and the machine-generated HTML, 104 | PostScript or PDF produced by some word processors for 105 | output purposes only. 106 | 107 | The ``Title Page'' means, for a printed book, the title page itself, 108 | plus such following pages as are needed to hold, legibly, the material 109 | this License requires to appear in the title page. For works in 110 | formats which do not have any title page as such, ``Title Page'' means 111 | the text near the most prominent appearance of the work's title, 112 | preceding the beginning of the body of the text. 113 | 114 | The ``publisher'' means any person or entity that distributes copies 115 | of the Document to the public. 116 | 117 | A section ``Entitled XYZ'' means a named subunit of the Document whose 118 | title either is precisely XYZ or contains XYZ in parentheses following 119 | text that translates XYZ in another language. (Here XYZ stands for a 120 | specific section name mentioned below, such as ``Acknowledgements'', 121 | ``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title'' 122 | of such a section when you modify the Document means that it remains a 123 | section ``Entitled XYZ'' according to this definition. 124 | 125 | The Document may include Warranty Disclaimers next to the notice which 126 | states that this License applies to the Document. These Warranty 127 | Disclaimers are considered to be included by reference in this 128 | License, but only as regards disclaiming warranties: any other 129 | implication that these Warranty Disclaimers may have is void and has 130 | no effect on the meaning of this License. 131 | 132 | @item 133 | VERBATIM COPYING 134 | 135 | You may copy and distribute the Document in any medium, either 136 | commercially or noncommercially, provided that this License, the 137 | copyright notices, and the license notice saying this License applies 138 | to the Document are reproduced in all copies, and that you add no other 139 | conditions whatsoever to those of this License. You may not use 140 | technical measures to obstruct or control the reading or further 141 | copying of the copies you make or distribute. However, you may accept 142 | compensation in exchange for copies. If you distribute a large enough 143 | number of copies you must also follow the conditions in section 3. 144 | 145 | You may also lend copies, under the same conditions stated above, and 146 | you may publicly display copies. 147 | 148 | @item 149 | COPYING IN QUANTITY 150 | 151 | If you publish printed copies (or copies in media that commonly have 152 | printed covers) of the Document, numbering more than 100, and the 153 | Document's license notice requires Cover Texts, you must enclose the 154 | copies in covers that carry, clearly and legibly, all these Cover 155 | Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on 156 | the back cover. Both covers must also clearly and legibly identify 157 | you as the publisher of these copies. The front cover must present 158 | the full title with all words of the title equally prominent and 159 | visible. You may add other material on the covers in addition. 160 | Copying with changes limited to the covers, as long as they preserve 161 | the title of the Document and satisfy these conditions, can be treated 162 | as verbatim copying in other respects. 163 | 164 | If the required texts for either cover are too voluminous to fit 165 | legibly, you should put the first ones listed (as many as fit 166 | reasonably) on the actual cover, and continue the rest onto adjacent 167 | pages. 168 | 169 | If you publish or distribute Opaque copies of the Document numbering 170 | more than 100, you must either include a machine-readable Transparent 171 | copy along with each Opaque copy, or state in or with each Opaque copy 172 | a computer-network location from which the general network-using 173 | public has access to download using public-standard network protocols 174 | a complete Transparent copy of the Document, free of added material. 175 | If you use the latter option, you must take reasonably prudent steps, 176 | when you begin distribution of Opaque copies in quantity, to ensure 177 | that this Transparent copy will remain thus accessible at the stated 178 | location until at least one year after the last time you distribute an 179 | Opaque copy (directly or through your agents or retailers) of that 180 | edition to the public. 181 | 182 | It is requested, but not required, that you contact the authors of the 183 | Document well before redistributing any large number of copies, to give 184 | them a chance to provide you with an updated version of the Document. 185 | 186 | @item 187 | MODIFICATIONS 188 | 189 | You may copy and distribute a Modified Version of the Document under 190 | the conditions of sections 2 and 3 above, provided that you release 191 | the Modified Version under precisely this License, with the Modified 192 | Version filling the role of the Document, thus licensing distribution 193 | and modification of the Modified Version to whoever possesses a copy 194 | of it. In addition, you must do these things in the Modified Version: 195 | 196 | @enumerate A 197 | @item 198 | Use in the Title Page (and on the covers, if any) a title distinct 199 | from that of the Document, and from those of previous versions 200 | (which should, if there were any, be listed in the History section 201 | of the Document). You may use the same title as a previous version 202 | if the original publisher of that version gives permission. 203 | 204 | @item 205 | List on the Title Page, as authors, one or more persons or entities 206 | responsible for authorship of the modifications in the Modified 207 | Version, together with at least five of the principal authors of the 208 | Document (all of its principal authors, if it has fewer than five), 209 | unless they release you from this requirement. 210 | 211 | @item 212 | State on the Title page the name of the publisher of the 213 | Modified Version, as the publisher. 214 | 215 | @item 216 | Preserve all the copyright notices of the Document. 217 | 218 | @item 219 | Add an appropriate copyright notice for your modifications 220 | adjacent to the other copyright notices. 221 | 222 | @item 223 | Include, immediately after the copyright notices, a license notice 224 | giving the public permission to use the Modified Version under the 225 | terms of this License, in the form shown in the Addendum below. 226 | 227 | @item 228 | Preserve in that license notice the full lists of Invariant Sections 229 | and required Cover Texts given in the Document's license notice. 230 | 231 | @item 232 | Include an unaltered copy of this License. 233 | 234 | @item 235 | Preserve the section Entitled ``History'', Preserve its Title, and add 236 | to it an item stating at least the title, year, new authors, and 237 | publisher of the Modified Version as given on the Title Page. If 238 | there is no section Entitled ``History'' in the Document, create one 239 | stating the title, year, authors, and publisher of the Document as 240 | given on its Title Page, then add an item describing the Modified 241 | Version as stated in the previous sentence. 242 | 243 | @item 244 | Preserve the network location, if any, given in the Document for 245 | public access to a Transparent copy of the Document, and likewise 246 | the network locations given in the Document for previous versions 247 | it was based on. These may be placed in the ``History'' section. 248 | You may omit a network location for a work that was published at 249 | least four years before the Document itself, or if the original 250 | publisher of the version it refers to gives permission. 251 | 252 | @item 253 | For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve 254 | the Title of the section, and preserve in the section all the 255 | substance and tone of each of the contributor acknowledgements and/or 256 | dedications given therein. 257 | 258 | @item 259 | Preserve all the Invariant Sections of the Document, 260 | unaltered in their text and in their titles. Section numbers 261 | or the equivalent are not considered part of the section titles. 262 | 263 | @item 264 | Delete any section Entitled ``Endorsements''. Such a section 265 | may not be included in the Modified Version. 266 | 267 | @item 268 | Do not retitle any existing section to be Entitled ``Endorsements'' or 269 | to conflict in title with any Invariant Section. 270 | 271 | @item 272 | Preserve any Warranty Disclaimers. 273 | @end enumerate 274 | 275 | If the Modified Version includes new front-matter sections or 276 | appendices that qualify as Secondary Sections and contain no material 277 | copied from the Document, you may at your option designate some or all 278 | of these sections as invariant. To do this, add their titles to the 279 | list of Invariant Sections in the Modified Version's license notice. 280 | These titles must be distinct from any other section titles. 281 | 282 | You may add a section Entitled ``Endorsements'', provided it contains 283 | nothing but endorsements of your Modified Version by various 284 | parties---for example, statements of peer review or that the text has 285 | been approved by an organization as the authoritative definition of a 286 | standard. 287 | 288 | You may add a passage of up to five words as a Front-Cover Text, and a 289 | passage of up to 25 words as a Back-Cover Text, to the end of the list 290 | of Cover Texts in the Modified Version. Only one passage of 291 | Front-Cover Text and one of Back-Cover Text may be added by (or 292 | through arrangements made by) any one entity. If the Document already 293 | includes a cover text for the same cover, previously added by you or 294 | by arrangement made by the same entity you are acting on behalf of, 295 | you may not add another; but you may replace the old one, on explicit 296 | permission from the previous publisher that added the old one. 297 | 298 | The author(s) and publisher(s) of the Document do not by this License 299 | give permission to use their names for publicity for or to assert or 300 | imply endorsement of any Modified Version. 301 | 302 | @item 303 | COMBINING DOCUMENTS 304 | 305 | You may combine the Document with other documents released under this 306 | License, under the terms defined in section 4 above for modified 307 | versions, provided that you include in the combination all of the 308 | Invariant Sections of all of the original documents, unmodified, and 309 | list them all as Invariant Sections of your combined work in its 310 | license notice, and that you preserve all their Warranty Disclaimers. 311 | 312 | The combined work need only contain one copy of this License, and 313 | multiple identical Invariant Sections may be replaced with a single 314 | copy. If there are multiple Invariant Sections with the same name but 315 | different contents, make the title of each such section unique by 316 | adding at the end of it, in parentheses, the name of the original 317 | author or publisher of that section if known, or else a unique number. 318 | Make the same adjustment to the section titles in the list of 319 | Invariant Sections in the license notice of the combined work. 320 | 321 | In the combination, you must combine any sections Entitled ``History'' 322 | in the various original documents, forming one section Entitled 323 | ``History''; likewise combine any sections Entitled ``Acknowledgements'', 324 | and any sections Entitled ``Dedications''. You must delete all 325 | sections Entitled ``Endorsements.'' 326 | 327 | @item 328 | COLLECTIONS OF DOCUMENTS 329 | 330 | You may make a collection consisting of the Document and other documents 331 | released under this License, and replace the individual copies of this 332 | License in the various documents with a single copy that is included in 333 | the collection, provided that you follow the rules of this License for 334 | verbatim copying of each of the documents in all other respects. 335 | 336 | You may extract a single document from such a collection, and distribute 337 | it individually under this License, provided you insert a copy of this 338 | License into the extracted document, and follow this License in all 339 | other respects regarding verbatim copying of that document. 340 | 341 | @item 342 | AGGREGATION WITH INDEPENDENT WORKS 343 | 344 | A compilation of the Document or its derivatives with other separate 345 | and independent documents or works, in or on a volume of a storage or 346 | distribution medium, is called an ``aggregate'' if the copyright 347 | resulting from the compilation is not used to limit the legal rights 348 | of the compilation's users beyond what the individual works permit. 349 | When the Document is included in an aggregate, this License does not 350 | apply to the other works in the aggregate which are not themselves 351 | derivative works of the Document. 352 | 353 | If the Cover Text requirement of section 3 is applicable to these 354 | copies of the Document, then if the Document is less than one half of 355 | the entire aggregate, the Document's Cover Texts may be placed on 356 | covers that bracket the Document within the aggregate, or the 357 | electronic equivalent of covers if the Document is in electronic form. 358 | Otherwise they must appear on printed covers that bracket the whole 359 | aggregate. 360 | 361 | @item 362 | TRANSLATION 363 | 364 | Translation is considered a kind of modification, so you may 365 | distribute translations of the Document under the terms of section 4. 366 | Replacing Invariant Sections with translations requires special 367 | permission from their copyright holders, but you may include 368 | translations of some or all Invariant Sections in addition to the 369 | original versions of these Invariant Sections. You may include a 370 | translation of this License, and all the license notices in the 371 | Document, and any Warranty Disclaimers, provided that you also include 372 | the original English version of this License and the original versions 373 | of those notices and disclaimers. In case of a disagreement between 374 | the translation and the original version of this License or a notice 375 | or disclaimer, the original version will prevail. 376 | 377 | If a section in the Document is Entitled ``Acknowledgements'', 378 | ``Dedications'', or ``History'', the requirement (section 4) to Preserve 379 | its Title (section 1) will typically require changing the actual 380 | title. 381 | 382 | @item 383 | TERMINATION 384 | 385 | You may not copy, modify, sublicense, or distribute the Document 386 | except as expressly provided under this License. Any attempt 387 | otherwise to copy, modify, sublicense, or distribute it is void, and 388 | will automatically terminate your rights under this License. 389 | 390 | However, if you cease all violation of this License, then your license 391 | from a particular copyright holder is reinstated (a) provisionally, 392 | unless and until the copyright holder explicitly and finally 393 | terminates your license, and (b) permanently, if the copyright holder 394 | fails to notify you of the violation by some reasonable means prior to 395 | 60 days after the cessation. 396 | 397 | Moreover, your license from a particular copyright holder is 398 | reinstated permanently if the copyright holder notifies you of the 399 | violation by some reasonable means, this is the first time you have 400 | received notice of violation of this License (for any work) from that 401 | copyright holder, and you cure the violation prior to 30 days after 402 | your receipt of the notice. 403 | 404 | Termination of your rights under this section does not terminate the 405 | licenses of parties who have received copies or rights from you under 406 | this License. If your rights have been terminated and not permanently 407 | reinstated, receipt of a copy of some or all of the same material does 408 | not give you any rights to use it. 409 | 410 | @item 411 | FUTURE REVISIONS OF THIS LICENSE 412 | 413 | The Free Software Foundation may publish new, revised versions 414 | of the GNU Free Documentation License from time to time. Such new 415 | versions will be similar in spirit to the present version, but may 416 | differ in detail to address new problems or concerns. See 417 | @uref{http://www.gnu.org/copyleft/}. 418 | 419 | Each version of the License is given a distinguishing version number. 420 | If the Document specifies that a particular numbered version of this 421 | License ``or any later version'' applies to it, you have the option of 422 | following the terms and conditions either of that specified version or 423 | of any later version that has been published (not as a draft) by the 424 | Free Software Foundation. If the Document does not specify a version 425 | number of this License, you may choose any version ever published (not 426 | as a draft) by the Free Software Foundation. If the Document 427 | specifies that a proxy can decide which future versions of this 428 | License can be used, that proxy's public statement of acceptance of a 429 | version permanently authorizes you to choose that version for the 430 | Document. 431 | 432 | @item 433 | RELICENSING 434 | 435 | ``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any 436 | World Wide Web server that publishes copyrightable works and also 437 | provides prominent facilities for anybody to edit those works. A 438 | public wiki that anybody can edit is an example of such a server. A 439 | ``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the 440 | site means any set of copyrightable works thus published on the MMC 441 | site. 442 | 443 | ``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0 444 | license published by Creative Commons Corporation, a not-for-profit 445 | corporation with a principal place of business in San Francisco, 446 | California, as well as future copyleft versions of that license 447 | published by that same organization. 448 | 449 | ``Incorporate'' means to publish or republish a Document, in whole or 450 | in part, as part of another Document. 451 | 452 | An MMC is ``eligible for relicensing'' if it is licensed under this 453 | License, and if all works that were first published under this License 454 | somewhere other than this MMC, and subsequently incorporated in whole 455 | or in part into the MMC, (1) had no cover texts or invariant sections, 456 | and (2) were thus incorporated prior to November 1, 2008. 457 | 458 | The operator of an MMC Site may republish an MMC contained in the site 459 | under CC-BY-SA on the same site at any time before August 1, 2009, 460 | provided the MMC is eligible for relicensing. 461 | 462 | @end enumerate 463 | 464 | @page 465 | @heading ADDENDUM: How to use this License for your documents 466 | 467 | To use this License in a document you have written, include a copy of 468 | the License in the document and put the following copyright and 469 | license notices just after the title page: 470 | 471 | @smallexample 472 | @group 473 | Copyright (C) @var{year} @var{your name}. 474 | Permission is granted to copy, distribute and/or modify this document 475 | under the terms of the GNU Free Documentation License, Version 1.3 476 | or any later version published by the Free Software Foundation; 477 | with no Invariant Sections, no Front-Cover Texts, and no Back-Cover 478 | Texts. A copy of the license is included in the section entitled ``GNU 479 | Free Documentation License''. 480 | @end group 481 | @end smallexample 482 | 483 | If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, 484 | replace the ``with@dots{}Texts.''@: line with this: 485 | 486 | @smallexample 487 | @group 488 | with the Invariant Sections being @var{list their titles}, with 489 | the Front-Cover Texts being @var{list}, and with the Back-Cover Texts 490 | being @var{list}. 491 | @end group 492 | @end smallexample 493 | 494 | If you have Invariant Sections without Cover Texts, or some other 495 | combination of the three, merge those two alternatives to suit the 496 | situation. 497 | 498 | If your document contains nontrivial examples of program code, we 499 | recommend releasing these examples in parallel under your choice of 500 | free software license, such as the GNU General Public License, 501 | to permit their use in free software. 502 | 503 | @c Local Variables: 504 | @c ispell-local-pdict: "ispell-dict" 505 | @c End: 506 | -------------------------------------------------------------------------------- /libbus.7: -------------------------------------------------------------------------------- 1 | .TH LIBBUS 7 BUS 2 | .SH NAME 3 | libbus - A simple daemonless system for broadcasting messages locally 4 | .SH DESCRIPTION 5 | .BR bus 6 | is a stupid-simple, thrilless, daemonless interprocess communication 7 | system for broadcasting messages. 8 | .SH RATIONALE 9 | We need an interprocess communication system similar to message queues. 10 | But we need broadcasting rather than anycasting, so we have a fast, 11 | simple and daemonless system for announcing events to any processes that 12 | might be interested. 13 | .SH FUTURE DIRECTION 14 | None. 15 | .SH SEE ALSO 16 | .BR bus (1), 17 | .BR bus (5), 18 | .BR bus_create (3), 19 | .BR bus_unlink (3), 20 | .BR bus_open (3), 21 | .BR bus_close (3), 22 | .BR bus_write (3), 23 | .BR bus_write_timed (3), 24 | .BR bus_read (3), 25 | .BR bus_read_timed (3), 26 | .BR bus_poll_start (3), 27 | .BR bus_poll_stop (3), 28 | .BR bus_poll (3), 29 | .BR bus_poll_timed (3), 30 | .BR bus_chown (3), 31 | .BR bus_chmod (3) 32 | -------------------------------------------------------------------------------- /libbus.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include "bus.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | #ifdef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_EVEN_HARDER 20 | # ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER 21 | # define BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER 22 | # endif 23 | #endif 24 | #ifdef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER 25 | # ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS 26 | # define BUS_SEMAPHORES_ARE_SYNCHRONOUS 27 | # endif 28 | #endif 29 | 30 | 31 | /** 32 | * Semaphore used to signal `bus_write` that `bus_read` is ready 33 | */ 34 | #define S 0 35 | 36 | /** 37 | * Semaphore for making `bus_write` wait while `bus_read` is reseting `S` 38 | */ 39 | #define W 1 40 | 41 | /** 42 | * Binary semaphore for making `bus_write` exclusively locked 43 | */ 44 | #define X 2 45 | 46 | /** 47 | * Semaphore used to cue `bus_read` that it may read the shared memory 48 | */ 49 | #define Q 3 50 | 51 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_EVEN_HARDER 52 | /** 53 | * Semaphore used to notify `bus_read` that it may restore `S` 54 | */ 55 | # define N 4 56 | 57 | /** 58 | * The number of semaphores in the semaphore array 59 | */ 60 | # define BUS_SEMAPHORES 5 61 | #else 62 | # define BUS_SEMAPHORES 4 63 | #endif 64 | 65 | /** 66 | * The default permission mits of the bus 67 | */ 68 | #define DEFAULT_MODE 0600 69 | 70 | 71 | 72 | /** 73 | * Decrease the value of a semaphore by 1 74 | * 75 | * @param bus:const bus_t * The bus 76 | * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` 77 | * @param flags:int `SEM_UNDO` if the action should be undone when the program exits, 78 | * `IPC_NOWAIT` if the action should fail if it would block 79 | * @return :int 0 on success, -1 on error 80 | */ 81 | #define acquire_semaphore(bus, semaphore, flags) \ 82 | semaphore_op(bus, semaphore, -1, flags) 83 | 84 | /** 85 | * Increase the value of a semaphore by 1 86 | * 87 | * @param bus:const bus_t * The bus 88 | * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` 89 | * @param flags:int `SEM_UNDO` if the action should be undone when the program exits 90 | * @return :int 0 on success, -1 on error 91 | */ 92 | #define release_semaphore(bus, semaphore, flags) \ 93 | semaphore_op(bus, semaphore, +1, flags) 94 | 95 | /** 96 | * Wait for the value of a semaphore to become 0 97 | * 98 | * @param bus:const bus_t * The bus 99 | * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` 100 | * @param flags:int `IPC_NOWAIT` if the action should fail if it would block 101 | * @return :int 0 on success, -1 on error 102 | */ 103 | #define zero_semaphore(bus, semaphore, flags) \ 104 | semaphore_op(bus, semaphore, 0, flags) 105 | 106 | /** 107 | * Decrease the value of a semaphore by 1 108 | * 109 | * @param bus:const bus_t * The bus 110 | * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` 111 | * @param flags:int `SEM_UNDO` if the action should be undone when the program exits, 112 | * `IPC_NOWAIT` if the action should fail if it would block 113 | * @param timeout:const struct timespec * The amount of time to wait before failing 114 | * @return :int 0 on success, -1 on error 115 | */ 116 | #define acquire_semaphore_timed(bus, semaphore, flags, timeout) \ 117 | semaphore_op_timed(bus, semaphore, -1, flags, timeout) 118 | 119 | /** 120 | * Increase the value of a semaphore by 1 121 | * 122 | * @param bus:const bus_t * The bus 123 | * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` 124 | * @param flags:int `SEM_UNDO` if the action should be undone when the program exits 125 | * @param timeout:const struct timespec * The amount of time to wait before failing 126 | * @return :int 0 on success, -1 on error 127 | */ 128 | #define release_semaphore_timed(bus, semaphore, flags, timeout) \ 129 | semaphore_op_timed(bus, semaphore, +1, flags, timeout) 130 | 131 | /** 132 | * Wait for the value of a semaphore to become 0 133 | * 134 | * @param bus:const bus_t * The bus 135 | * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` 136 | * @param flags:int `IPC_NOWAIT` if the action should fail if it would block 137 | * @param timeout:const struct timespec * The amount of time to wait before failing 138 | * @return :int 0 on success, -1 on error 139 | */ 140 | #define zero_semaphore_timed(bus, semaphore, flags, timeout) \ 141 | semaphore_op_timed(bus, semaphore, 0, flags, timeout) 142 | 143 | /** 144 | * Open the semaphore array 145 | * 146 | * @param bus:const bus_t * The bus 147 | * @return :int 0 on success, -1 on error 148 | */ 149 | #define open_semaphores(bus) \ 150 | (((bus)->sem_id = semget((bus)->key_sem, BUS_SEMAPHORES, 0)) == -1 ? -1 : 0) 151 | 152 | /** 153 | * Write a message to the shared memory 154 | * 155 | * @param bus:const bus_t * The bus 156 | * @param msg:const char * The message 157 | * @return :int 0 on success, -1 on error 158 | */ 159 | #define write_shared_memory(bus, msg) \ 160 | (memcpy((bus)->message, msg, (strlen(msg) + 1) * sizeof(char))) 161 | 162 | 163 | /** 164 | * Set `delta` to the convertion of `timeout` from absolute to relative time, 165 | * measured in the clock whose ID is specified by `clockid` 166 | * 167 | * @scope timeout:struct timespec Output variable for relative time 168 | * @scope timeout:const struct timespec * The absolute time 169 | * @scope clockid:clockid_t The clock time is measured 170 | */ 171 | #define DELTA \ 172 | do { \ 173 | if (absolute_time_to_delta_time(&delta, timeout, clockid) < 0) { \ 174 | goto fail; \ 175 | } else if ((delta.tv_sec < 0) || (delta.tv_nsec < 0)) { \ 176 | errno = EAGAIN; \ 177 | goto fail; \ 178 | }\ 179 | } while (0) 180 | 181 | 182 | /** 183 | * If `flags & (bus_flag)`, this macro evalutes to `sys_flag`, 184 | * otherwise this macro evalutes to 0. 185 | */ 186 | #define F(bus_flag, sys_flag) \ 187 | ((flags & (bus_flag)) ? sys_flag : 0) 188 | 189 | 190 | 191 | /** 192 | * Statement wrapper that goes to `fail` on failure 193 | */ 194 | #define t(inst) \ 195 | do { if ((inst) == -1) goto fail; } while (0) 196 | 197 | 198 | 199 | #ifndef SEMUN_ALREADY_DEFINED 200 | union semun { 201 | int val; 202 | struct semid_ds *buf; 203 | unsigned short *array; 204 | }; 205 | #endif 206 | 207 | 208 | 209 | /** 210 | * Create a semaphore array for the bus 211 | * 212 | * @param bus Bus information to fill with the key of the created semaphore array 213 | * @return 0 on success, -1 on error 214 | */ 215 | static int 216 | create_semaphores(bus_t *bus) 217 | { 218 | int id = -1, rint, saved_errno; 219 | double r; 220 | union semun values; 221 | 222 | values.array = NULL; 223 | 224 | /* Create semaphore array. */ 225 | for (;;) { 226 | rint = rand(); 227 | r = (double)rint; 228 | r /= (double)RAND_MAX + 1; 229 | r *= (1 << (8 * sizeof(key_t) - 2)) - 1; 230 | bus->key_sem = (key_t)r + 1; 231 | if (bus->key_sem == IPC_PRIVATE) 232 | continue; 233 | id = semget(bus->key_sem, BUS_SEMAPHORES, IPC_CREAT | IPC_EXCL | DEFAULT_MODE); 234 | if (id != -1) 235 | break; 236 | if ((errno != EEXIST) && (errno != EINTR)) 237 | goto fail; 238 | } 239 | 240 | /* Initialise the array. */ 241 | values.array = calloc((size_t)BUS_SEMAPHORES, sizeof(unsigned short)); 242 | if (!values.array) 243 | goto fail; 244 | values.array[X] = 1; 245 | if (semctl(id, 0, SETALL, values.array) == -1) 246 | goto fail; 247 | free(values.array); 248 | values.array = NULL; 249 | 250 | return 0; 251 | 252 | fail: 253 | saved_errno = errno; 254 | if (id != -1) 255 | semctl(id, 0, IPC_RMID); 256 | free(values.array); 257 | errno = saved_errno; 258 | return -1; 259 | } 260 | 261 | 262 | /** 263 | * Create a shared memory for the bus 264 | * 265 | * @param bus Bus information to fill with the key of the created shared memory 266 | * @return 0 on success, -1 on error 267 | */ 268 | static int 269 | create_shared_memory(bus_t *bus) 270 | { 271 | int id = -1, rint, saved_errno; 272 | double r; 273 | struct shmid_ds _info; 274 | 275 | /* Create shared memory. */ 276 | for (;;) { 277 | rint = rand(); 278 | r = (double)rint; 279 | r /= (double)RAND_MAX + 1; 280 | r *= (1 << (8 * sizeof(key_t) - 2)) - 1; 281 | bus->key_shm = (key_t)r + 1; 282 | if (bus->key_shm == IPC_PRIVATE) 283 | continue; 284 | id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, IPC_CREAT | IPC_EXCL | DEFAULT_MODE); 285 | if (id != -1) 286 | break; 287 | if ((errno != EEXIST) && (errno != EINTR)) 288 | goto fail; 289 | } 290 | 291 | return 0; 292 | 293 | fail: 294 | saved_errno = errno; 295 | if (id != -1) 296 | shmctl(id, IPC_RMID, &_info); 297 | errno = saved_errno; 298 | return -1; 299 | } 300 | 301 | 302 | /** 303 | * Remove the semaphore array for the bus 304 | * 305 | * @param bus Bus information 306 | * @return 0 on success, -1 on error 307 | */ 308 | static int 309 | remove_semaphores(const bus_t *bus) 310 | { 311 | int id = semget(bus->key_sem, BUS_SEMAPHORES, 0); 312 | return ((id == -1) || (semctl(id, 0, IPC_RMID) == -1)) ? -1 : 0; 313 | } 314 | 315 | 316 | /** 317 | * Remove the shared memory for the bus 318 | * 319 | * @param bus Bus information 320 | * @return 0 on success, -1 on error 321 | */ 322 | static int 323 | remove_shared_memory(const bus_t *bus) 324 | { 325 | struct shmid_ds _info; 326 | int id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, 0); 327 | return ((id == -1) || (shmctl(id, IPC_RMID, &_info) == -1)) ? -1 : 0; 328 | } 329 | 330 | 331 | /** 332 | * Increase or decrease the value of a semaphore, or wait the it to become 0 333 | * 334 | * @param bus Bus information 335 | * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` 336 | * @param delta The adjustment to make to the semaphore's value, 0 to wait for it to become 0 337 | * @param flags `SEM_UNDO` if the action should be undone when the program exits 338 | * @return 0 on success, -1 on error 339 | */ 340 | static int 341 | semaphore_op(const bus_t *bus, int semaphore, int delta, int flags) 342 | { 343 | struct sembuf op; 344 | op.sem_num = (unsigned short)semaphore; 345 | op.sem_op = (short)delta; 346 | op.sem_flg = (short)flags; 347 | return semop(bus->sem_id, &op, (size_t)1); 348 | } 349 | 350 | 351 | /** 352 | * Increase or decrease the value of a semaphore, or wait the it to become 0 353 | * 354 | * @param bus Bus information 355 | * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` 356 | * @param delta The adjustment to make to the semaphore's value, 0 to wait for it to become 0 357 | * @param flags `SEM_UNDO` if the action should be undone when the program exits 358 | * @param timeout The amount of time to wait before failing 359 | * @return 0 on success, -1 on error 360 | */ 361 | static int 362 | semaphore_op_timed(const bus_t *bus, int semaphore, int delta, int flags, const struct timespec *timeout) 363 | { 364 | struct sembuf op; 365 | op.sem_num = (unsigned short)semaphore; 366 | op.sem_op = (short)delta; 367 | op.sem_flg = (short)flags; 368 | return semtimedop(bus->sem_id, &op, (size_t)1, timeout); 369 | } 370 | 371 | 372 | /** 373 | * Set the value of a semaphore 374 | * 375 | * @param bus Bus information 376 | * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` 377 | * @param value The new value of the semaphore 378 | * @return 0 on success, -1 on error 379 | */ 380 | static int 381 | write_semaphore(const bus_t *bus, unsigned semaphore, int value) 382 | { 383 | union semun semval; 384 | semval.val = value; 385 | return semctl(bus->sem_id, (unsigned short)semaphore, SETVAL, semval); 386 | } 387 | 388 | 389 | /** 390 | * Open the shared memory for the bus 391 | * 392 | * @param bus Bus information 393 | * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR` 394 | * @return 0 on success, -1 on error 395 | */ 396 | static int 397 | open_shared_memory(bus_t *bus, int flags) 398 | { 399 | int id; 400 | void *address; 401 | t(id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, 0)); 402 | address = shmat(id, NULL, (flags & BUS_RDONLY) ? SHM_RDONLY : 0); 403 | if ((address == (void *)-1) || !address) 404 | goto fail; 405 | bus->message = (char *)address; 406 | return 0; 407 | fail: 408 | return -1; 409 | } 410 | 411 | 412 | /** 413 | * Close the shared memory for the bus 414 | * 415 | * @param bus Bus information 416 | * @return 0 on success, -1 on error 417 | */ 418 | static int 419 | close_shared_memory(bus_t *bus) 420 | { 421 | t(shmdt(bus->message)); 422 | bus->message = NULL; 423 | return 0; 424 | fail: 425 | return -1; 426 | } 427 | 428 | 429 | /** 430 | * Get a random ASCII letter or digit 431 | * 432 | * @return A random ASCII letter or digit 433 | */ 434 | static char 435 | randomchar(void) 436 | { 437 | int rint = rand(); 438 | double r = (double)rint; 439 | r /= (double)RAND_MAX + 1; 440 | r *= 10 + 26 + 26; 441 | return "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"[(int)r]; 442 | } 443 | 444 | 445 | /** 446 | * Basically, this is `mkdir -p -m $mode $pathname` 447 | * 448 | * @param pathname The pathname of the directory to create if missing 449 | * @param mode The permission bits of any created directory 450 | * @return 0 on sucess, -1 on error 451 | */ 452 | static int 453 | mkdirs(char *pathname, mode_t mode) 454 | { 455 | size_t i, n = strlen(pathname); 456 | char c; 457 | for (i = 0; i < n; i++) 458 | if (pathname[i] != '/') 459 | break; 460 | for (; i < n; i++) { 461 | if (pathname[i] == '/') { 462 | c = pathname[i]; 463 | if (access(pathname, F_OK)) 464 | if (mkdir(pathname, mode) < 0) 465 | return -1; 466 | pathname[i] = c; 467 | break; 468 | } 469 | } 470 | if (access(pathname, F_OK)) 471 | if (mkdir(pathname, mode) < 0) 472 | return -1; 473 | return 0; 474 | } 475 | 476 | 477 | /** 478 | * Convert an absolute time to a relative time 479 | * 480 | * @param delta Output parameter for the relative time 481 | * @param absolute The absolute time 482 | * @param clockid The ID of the clock the time is measured in 483 | * @return 0 on success, -1 on error 484 | */ 485 | static int 486 | absolute_time_to_delta_time(struct timespec *delta, const struct timespec *absolute, clockid_t clockid) 487 | { 488 | if (clock_gettime(clockid, delta) < 0) 489 | return -1; 490 | 491 | delta->tv_sec = absolute->tv_sec - delta->tv_sec; 492 | delta->tv_nsec = absolute->tv_nsec - delta->tv_nsec; 493 | 494 | if (delta->tv_nsec < 0L) { 495 | delta->tv_nsec += 1000000000L; 496 | delta->tv_sec -= 1; 497 | } 498 | if (delta->tv_nsec >= 1000000000L) { 499 | delta->tv_nsec -= 1000000000L; 500 | delta->tv_sec += 1; 501 | } 502 | 503 | return 0; 504 | } 505 | 506 | 507 | 508 | /** 509 | * Create a new bus 510 | * 511 | * @param file The pathname of the bus, `NULL` to create a random one 512 | * @param flags `BUS_EXCL` (if `file` is not `NULL`) to fail if the file 513 | * already exists, otherwise if the file exists, nothing 514 | * will happen; 515 | * `BUS_INTR` to fail if interrupted 516 | * @param out_file Output parameter for the pathname of the bus 517 | * @return 0 on success, -1 on error 518 | */ 519 | int 520 | bus_create(const char *restrict file, int flags, char **restrict out_file) 521 | { 522 | int fd = -1, saved_errno; 523 | bus_t bus; 524 | char buf[1 + 2 * (3 * sizeof(ssize_t) + 2)]; 525 | size_t ptr, len; 526 | ssize_t wrote; 527 | char *genfile = NULL; 528 | const char *env; 529 | 530 | if (out_file) 531 | *out_file = NULL; 532 | 533 | bus.sem_id = -1; 534 | bus.key_sem = -1; 535 | bus.key_shm = -1; 536 | bus.message = NULL; 537 | bus.first_poll = 0; 538 | 539 | srand((unsigned int)time(NULL) + (unsigned int)rand()); 540 | 541 | if (file) { 542 | fd = open(file, O_WRONLY | O_CREAT | O_EXCL, DEFAULT_MODE); 543 | if (fd == -1) { 544 | if ((errno != EEXIST) || (flags & BUS_EXCL)) 545 | return -1; 546 | goto done; 547 | } 548 | } else { 549 | env = getenv("XDG_RUNTIME_DIR"); 550 | if (!env || !*env) 551 | env = "/run"; 552 | genfile = malloc((strlen(env) + 6 + 7 + 30) * sizeof(char)); 553 | if (!genfile) 554 | goto fail; 555 | if (out_file) 556 | *out_file = genfile; 557 | sprintf(genfile, "%s/bus", env); 558 | t(mkdirs(genfile, 0755)); 559 | sprintf(genfile, "%s/bus/random.", env); 560 | len = strlen(genfile); 561 | genfile[len + 30] = '\0'; 562 | retry: 563 | for (ptr = 0; ptr < 30; ptr++) 564 | genfile[len + ptr] = randomchar(); 565 | fd = open(genfile, O_WRONLY | O_CREAT | O_EXCL, DEFAULT_MODE); 566 | if (fd == -1) { 567 | if (errno == EEXIST) 568 | goto retry; 569 | return -1; 570 | } 571 | } 572 | 573 | t(create_semaphores(&bus)); 574 | t(create_shared_memory(&bus)); 575 | 576 | sprintf(buf, "%zi\n%zi\n", (ssize_t)(bus.key_sem), (ssize_t)(bus.key_shm)); 577 | for (len = strlen(buf), ptr = 0; ptr < len;) { 578 | wrote = write(fd, buf + ptr, len - ptr); 579 | if (wrote < 0) { 580 | if ((errno != EINTR) || (flags & BUS_INTR)) 581 | goto fail; 582 | } else { 583 | ptr += (size_t)wrote; 584 | } 585 | } 586 | close(fd); 587 | 588 | done: 589 | if (out_file && !*out_file) { 590 | len = strlen(file) + 1; 591 | *out_file = malloc(len * sizeof(char)); 592 | memcpy(*out_file, file, len * sizeof(char)); 593 | } else if (!out_file) { 594 | free(genfile); 595 | } 596 | return 0; 597 | 598 | fail: 599 | saved_errno = errno; 600 | if (bus.key_sem) 601 | remove_semaphores(&bus); 602 | if (bus.key_shm) 603 | remove_shared_memory(&bus); 604 | if (fd == -1) 605 | close(fd); 606 | if (out_file) 607 | *out_file = NULL; 608 | free(genfile); 609 | unlink(file); 610 | errno = saved_errno; 611 | return -1; 612 | } 613 | 614 | 615 | /** 616 | * Remove a bus 617 | * 618 | * @param file The pathname of the bus 619 | * @return 0 on success, -1 on error 620 | */ 621 | int 622 | bus_unlink(const char *file) 623 | { 624 | int r = 0, saved_errno = 0; 625 | bus_t bus; 626 | t(bus_open(&bus, file, -1)); 627 | 628 | r |= remove_semaphores(&bus); 629 | if (r && !saved_errno) 630 | saved_errno = errno; 631 | 632 | r |= remove_shared_memory(&bus); 633 | if (r && !saved_errno) 634 | saved_errno = errno; 635 | 636 | r |= unlink(file); 637 | if (r && !saved_errno) 638 | saved_errno = errno; 639 | 640 | errno = saved_errno; 641 | return r; 642 | fail: 643 | return -1; 644 | } 645 | 646 | 647 | /** 648 | * Open an existing bus 649 | * 650 | * @param bus Bus information to fill 651 | * @param file The filename of the bus 652 | * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR` 653 | * any negative value is used internally 654 | * for telling the function to not actually 655 | * opening the bus, but just to parse the file 656 | * @return 0 on success, -1 on error 657 | */ 658 | int 659 | bus_open(bus_t *restrict bus, const char *restrict file, int flags) 660 | { 661 | int saved_errno; 662 | char *line = NULL; 663 | size_t len = 0; 664 | FILE *f; 665 | 666 | bus->sem_id = -1; 667 | bus->key_sem = -1; 668 | bus->key_shm = -1; 669 | bus->message = NULL; 670 | 671 | f = fopen(file, "r"); 672 | if (!f) 673 | goto fail; 674 | 675 | t(getline(&line, &len, f)); 676 | t(bus->key_sem = (key_t)atoll(line)); 677 | free(line), line = NULL, len = 0; 678 | 679 | t(getline(&line, &len, f)); 680 | t(bus->key_shm = (key_t)atoll(line)); 681 | free(line), line = NULL; 682 | 683 | fclose(f); 684 | 685 | if (flags >= 0) { 686 | t(open_semaphores(bus)); 687 | t(open_shared_memory(bus, flags)); 688 | } 689 | 690 | return 0; 691 | fail: 692 | saved_errno = errno; 693 | free(line); 694 | errno = saved_errno; 695 | return -1; 696 | } 697 | 698 | 699 | /** 700 | * Close a bus 701 | * 702 | * @param bus Bus information 703 | * @return 0 on success, -1 on error 704 | */ 705 | int 706 | bus_close(bus_t *bus) 707 | { 708 | bus->sem_id = -1; 709 | if (bus->message) 710 | t(close_shared_memory(bus)); 711 | bus->message = NULL; 712 | return 0; 713 | 714 | fail: 715 | return -1; 716 | } 717 | 718 | 719 | /** 720 | * Broadcast a message on a bus 721 | * 722 | * @param bus Bus information 723 | * @param message The message to write, may not be longer than 724 | * `BUS_MEMORY_SIZE` including the NUL-termination 725 | * @param flags `BUS_NOWAIT` if this function shall fail if 726 | * another process is currently running this 727 | * procedure 728 | * @return 0 on success, -1 on error 729 | */ 730 | int 731 | bus_write(const bus_t *bus, const char *message, int flags) 732 | { 733 | int saved_errno; 734 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS 735 | int state = 0; 736 | #endif 737 | if (acquire_semaphore(bus, X, SEM_UNDO | F(BUS_NOWAIT, IPC_NOWAIT)) == -1) 738 | return -1; 739 | t(zero_semaphore(bus, W, 0)); 740 | write_shared_memory(bus, message); 741 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS 742 | t(release_semaphore(bus, N, SEM_UNDO)); state++; 743 | #endif 744 | t(write_semaphore(bus, Q, 0)); 745 | t(zero_semaphore(bus, S, 0)); 746 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS 747 | t(acquire_semaphore(bus, N, SEM_UNDO)); state--; 748 | #endif 749 | t(release_semaphore(bus, X, SEM_UNDO)); 750 | return 0; 751 | 752 | fail: 753 | saved_errno = errno; 754 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS 755 | if (state > 0) 756 | acquire_semaphore(bus, N, SEM_UNDO); 757 | #endif 758 | release_semaphore(bus, X, SEM_UNDO); 759 | errno = saved_errno; 760 | return -1; 761 | } 762 | 763 | 764 | /** 765 | * Broadcast a message on a bus 766 | * 767 | * @param bus Bus information 768 | * @param message The message to write, may not be longer than 769 | * `BUS_MEMORY_SIZE` including the NUL-termination 770 | * @param timeout The time the operation shall fail with errno set 771 | * to `EAGAIN` if not completed 772 | * @param clockid The ID of the clock the `timeout` is measured with, 773 | * it most be a predictable clock 774 | * @return 0 on success, -1 on error 775 | */ 776 | int bus_write_timed(const bus_t *bus, const char *message, 777 | const struct timespec *timeout, clockid_t clockid) 778 | { 779 | int saved_errno; 780 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS 781 | int state = 0; 782 | #endif 783 | struct timespec delta; 784 | if (!timeout) 785 | return bus_write(bus, message, 0); 786 | 787 | DELTA; 788 | if (acquire_semaphore_timed(bus, X, SEM_UNDO, &delta) == -1) 789 | return -1; 790 | DELTA; 791 | t(zero_semaphore_timed(bus, W, 0, &delta)); 792 | write_shared_memory(bus, message); 793 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS 794 | t(release_semaphore(bus, N, SEM_UNDO)); state++; 795 | #endif 796 | t(write_semaphore(bus, Q, 0)); 797 | t(zero_semaphore(bus, S, 0)); 798 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS 799 | t(acquire_semaphore(bus, N, SEM_UNDO)); state--; 800 | #endif 801 | t(release_semaphore(bus, X, SEM_UNDO)); 802 | return 0; 803 | 804 | fail: 805 | saved_errno = errno; 806 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS 807 | if (state > 0) 808 | acquire_semaphore(bus, N, SEM_UNDO); 809 | #endif 810 | release_semaphore(bus, X, SEM_UNDO); 811 | errno = saved_errno; 812 | return -1; 813 | } 814 | 815 | 816 | /** 817 | * Listen (in a loop, forever) for new message on a bus 818 | * 819 | * @param bus Bus information 820 | * @param callback Function to call when a message is received, the 821 | * input parameters will be the read message and 822 | * `user_data` from `bus_read`'s parameter with the 823 | * same name. The message must have been parsed or 824 | * copied when `callback` returns as it may be over 825 | * overridden after that time. `callback` should 826 | * return either of the the values: 827 | * * 0: stop listening 828 | * * 1: continue listening 829 | * * -1: an error has occurred 830 | * However, the function [`bus_read`] will invoke 831 | * `callback` with `message` set to `NULL`one time 832 | * directly after it has started listening on the 833 | * bus. This is to the the program now it can safely 834 | * continue with any action that requires that the 835 | * programs is listening on the bus. 836 | * @param user_data Parameter passed to `callback` 837 | * @return 0 on success, -1 on error 838 | */ 839 | int 840 | bus_read(const bus_t *restrict bus, int (*callback)(const char *message, void *user_data), void *user_data) 841 | { 842 | int r, state = 0, saved_errno; 843 | if (release_semaphore(bus, S, SEM_UNDO) == -1) 844 | return -1; 845 | t(r = callback(NULL, user_data)); 846 | if (!r) goto done; 847 | for (;;) { 848 | t(release_semaphore(bus, Q, 0)); 849 | t(zero_semaphore(bus, Q, 0)); 850 | t(r = callback(bus->message, user_data)); 851 | if (!r) goto done; 852 | t(release_semaphore(bus, W, SEM_UNDO)); state++; 853 | t(acquire_semaphore(bus, S, SEM_UNDO)); state++; 854 | t(zero_semaphore(bus, S, 0)); 855 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER 856 | t(zero_semaphore(bus, N, 0)); 857 | #endif 858 | t(release_semaphore(bus, S, SEM_UNDO)); state--; 859 | t(acquire_semaphore(bus, W, SEM_UNDO)); state--; 860 | } 861 | 862 | fail: 863 | saved_errno = errno; 864 | if (state > 1) 865 | release_semaphore(bus, S, SEM_UNDO); 866 | if (state > 0) 867 | acquire_semaphore(bus, W, SEM_UNDO); 868 | acquire_semaphore(bus, S, SEM_UNDO); 869 | errno = saved_errno; 870 | return -1; 871 | 872 | done: 873 | t(acquire_semaphore(bus, S, SEM_UNDO)); 874 | return 0; 875 | } 876 | 877 | 878 | /** 879 | * Listen (in a loop, forever) for new message on a bus 880 | * 881 | * @param bus Bus information 882 | * @param callback Function to call when a message is received, the 883 | * input parameters will be the read message and 884 | * `user_data` from `bus_read`'s parameter with the 885 | * same name. The message must have been parsed or 886 | * copied when `callback` returns as it may be over 887 | * overridden after that time. `callback` should 888 | * return either of the the values: 889 | * * 0: stop listening 890 | * * 1: continue listening 891 | * * -1: an error has occurred 892 | * However, the function [`bus_read`] will invoke 893 | * `callback` with `message` set to `NULL`one time 894 | * directly after it has started listening on the 895 | * bus. This is to the the program now it can safely 896 | * continue with any action that requires that the 897 | * programs is listening on the bus. 898 | * @param user_data Parameter passed to `callback` 899 | * @param timeout The time the operation shall fail with errno set 900 | * to `EAGAIN` if not completed, note that the callback 901 | * function may or may not have been called 902 | * @param clockid The ID of the clock the `timeout` is measured with, 903 | * it most be a predictable clock 904 | * @return 0 on success, -1 on error 905 | */ 906 | int bus_read_timed(const bus_t *restrict bus, int (*callback)(const char *message, void *user_data), 907 | void *user_data, const struct timespec *timeout, clockid_t clockid) 908 | { 909 | int r, state = 0, saved_errno; 910 | struct timespec delta; 911 | if (!timeout) 912 | return bus_read(bus, callback, user_data); 913 | 914 | DELTA; 915 | if (release_semaphore_timed(bus, S, SEM_UNDO, &delta) == -1) 916 | return -1; 917 | t(r = callback(NULL, user_data)); 918 | if (!r) goto done; 919 | for (;;) { 920 | DELTA; 921 | t(release_semaphore_timed(bus, Q, 0, &delta)); 922 | DELTA; 923 | t(zero_semaphore_timed(bus, Q, 0, &delta)); 924 | t(r = callback(bus->message, user_data)); 925 | if (!r) goto done; 926 | t(release_semaphore(bus, W, SEM_UNDO)); state++; 927 | t(acquire_semaphore(bus, S, SEM_UNDO)); state++; 928 | t(zero_semaphore(bus, S, 0)); 929 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER 930 | t(zero_semaphore(bus, N, 0)); 931 | #endif 932 | t(release_semaphore(bus, S, SEM_UNDO)); state--; 933 | t(acquire_semaphore(bus, W, SEM_UNDO)); state--; 934 | } 935 | 936 | fail: 937 | saved_errno = errno; 938 | if (state > 1) 939 | release_semaphore(bus, S, SEM_UNDO); 940 | if (state > 0) 941 | acquire_semaphore(bus, W, SEM_UNDO); 942 | acquire_semaphore(bus, S, SEM_UNDO); 943 | errno = saved_errno; 944 | return -1; 945 | 946 | done: 947 | t(acquire_semaphore(bus, S, SEM_UNDO)); 948 | return 0; 949 | } 950 | 951 | 952 | /** 953 | * Announce that the thread is listening on the bus. 954 | * This is required so the will does not miss any 955 | * messages due to race conditions. Additionally, 956 | * not calling this function will cause the bus the 957 | * misbehave, is `bus_poll` is written to expect 958 | * this function to have been called. 959 | * 960 | * @param bus Bus information 961 | * @return 0 on success, -1 on error 962 | */ 963 | int 964 | bus_poll_start(bus_t *bus) 965 | { 966 | bus->first_poll = 1; 967 | t(release_semaphore(bus, S, SEM_UNDO)); 968 | t(release_semaphore(bus, Q, 0)); 969 | return 0; 970 | 971 | fail: 972 | return -1; 973 | } 974 | 975 | 976 | /** 977 | * Announce that the thread has stopped listening on the bus. 978 | * This is required so that the thread does not cause others 979 | * to wait indefinitely. 980 | * 981 | * @param bus Bus information 982 | * @return 0 on success, -1 on error 983 | */ 984 | int 985 | bus_poll_stop(const bus_t *bus) 986 | { 987 | return acquire_semaphore(bus, S, SEM_UNDO | IPC_NOWAIT); 988 | } 989 | 990 | 991 | /** 992 | * Wait for a message to be broadcasted on the bus. 993 | * The caller should make a copy of the received message, 994 | * without freeing the original copy, and parse it in a 995 | * separate thread. When the new thread has started be 996 | * started, the caller of this function should then 997 | * either call `bus_poll` again or `bus_poll_stop`. 998 | * 999 | * @param bus Bus information 1000 | * @param flags `BUS_NOWAIT` if the bus should fail and set `errno` to 1001 | * `EAGAIN` if there isn't already a message available on the bus 1002 | * @return The received message, `NULL` on error 1003 | */ 1004 | const char * 1005 | bus_poll(bus_t *bus, int flags) 1006 | { 1007 | int state = 0, saved_errno; 1008 | if (!bus->first_poll) { 1009 | t(release_semaphore(bus, W, SEM_UNDO)); state++; 1010 | t(acquire_semaphore(bus, S, SEM_UNDO)); state++; 1011 | t(zero_semaphore(bus, S, 0)); 1012 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER 1013 | t(zero_semaphore(bus, N, 0)); 1014 | #endif 1015 | t(release_semaphore(bus, S, SEM_UNDO)); state--; 1016 | t(acquire_semaphore(bus, W, SEM_UNDO)); state--; 1017 | t(release_semaphore(bus, Q, 0)); 1018 | } else { 1019 | bus->first_poll = 0; 1020 | } 1021 | state--; 1022 | t(zero_semaphore(bus, Q, F(BUS_NOWAIT, IPC_NOWAIT))); 1023 | return bus->message; 1024 | 1025 | fail: 1026 | saved_errno = errno; 1027 | if (state > 1) 1028 | release_semaphore(bus, S, SEM_UNDO); 1029 | if (state > 0) 1030 | acquire_semaphore(bus, W, SEM_UNDO); 1031 | if (state < 0) 1032 | bus->first_poll = 1; 1033 | errno = saved_errno; 1034 | return NULL; 1035 | } 1036 | 1037 | 1038 | /** 1039 | * Wait for a message to be broadcasted on the bus. 1040 | * The caller should make a copy of the received message, 1041 | * without freeing the original copy, and parse it in a 1042 | * separate thread. When the new thread has started be 1043 | * started, the caller of this function should then 1044 | * either call `bus_poll_timed` again or `bus_poll_stop`. 1045 | * 1046 | * @param bus Bus information 1047 | * @param timeout The time the operation shall fail with errno set 1048 | * to `EAGAIN` if not completed 1049 | * @param clockid The ID of the clock the `timeout` is measured with, 1050 | * it most be a predictable clock 1051 | * @return The received message, `NULL` on error 1052 | */ 1053 | const char *bus_poll_timed(bus_t *bus, const struct timespec *timeout, clockid_t clockid) 1054 | { 1055 | int state = 0, saved_errno; 1056 | struct timespec delta; 1057 | if (!timeout) 1058 | return bus_poll(bus, 0); 1059 | 1060 | if (!bus->first_poll) { 1061 | t(release_semaphore(bus, W, SEM_UNDO)); state++; 1062 | t(acquire_semaphore(bus, S, SEM_UNDO)); state++; 1063 | t(zero_semaphore(bus, S, 0)); 1064 | #ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER 1065 | t(zero_semaphore(bus, N, 0)); 1066 | #endif 1067 | t(release_semaphore(bus, S, SEM_UNDO)); state--; 1068 | t(acquire_semaphore(bus, W, SEM_UNDO)); state--; 1069 | t(release_semaphore(bus, Q, 0)); 1070 | } else { 1071 | bus->first_poll = 0; 1072 | } 1073 | state--; 1074 | DELTA; 1075 | t(zero_semaphore_timed(bus, Q, 0, &delta)); 1076 | return bus->message; 1077 | 1078 | fail: 1079 | saved_errno = errno; 1080 | if (state > 1) 1081 | release_semaphore(bus, S, SEM_UNDO); 1082 | if (state > 0) 1083 | acquire_semaphore(bus, W, SEM_UNDO); 1084 | if (state < 0) 1085 | bus->first_poll = 1; 1086 | errno = saved_errno; 1087 | return NULL; 1088 | } 1089 | 1090 | 1091 | /** 1092 | * Change the ownership of a bus 1093 | * 1094 | * `stat(2)` can be used of the bus's associated file to get the bus's ownership 1095 | * 1096 | * @param file The pathname of the bus 1097 | * @param owner The user ID of the bus's new owner 1098 | * @param group The group ID of the bus's new group 1099 | * @return 0 on success, -1 on error 1100 | */ 1101 | int 1102 | bus_chown(const char *file, uid_t owner, gid_t group) 1103 | { 1104 | bus_t bus; 1105 | struct semid_ds sem_stat; 1106 | struct shmid_ds shm_stat; 1107 | int shm_id; 1108 | 1109 | t(bus_open(&bus, file, -1)); 1110 | t(chown(file, owner, group)); 1111 | 1112 | /* chown sem */ 1113 | t(open_semaphores(&bus)); 1114 | t(semctl(bus.sem_id, 0, IPC_STAT, &sem_stat)); 1115 | sem_stat.sem_perm.uid = owner; 1116 | sem_stat.sem_perm.gid = group; 1117 | t(semctl(bus.sem_id, 0, IPC_SET, &sem_stat)); 1118 | 1119 | /* chown shm */ 1120 | t(shm_id = shmget(bus.key_shm, (size_t)BUS_MEMORY_SIZE, 0)); 1121 | t(shmctl(shm_id, IPC_STAT, &shm_stat)); 1122 | shm_stat.shm_perm.uid = owner; 1123 | shm_stat.shm_perm.gid = group; 1124 | t(shmctl(shm_id, IPC_SET, &shm_stat)); 1125 | 1126 | return 0; 1127 | fail: 1128 | return -1; 1129 | } 1130 | 1131 | 1132 | /** 1133 | * Change the permissions for a bus 1134 | * 1135 | * `stat(2)` can be used of the bus's associated file to get the bus's permissions 1136 | * 1137 | * @param file The pathname of the bus 1138 | * @param mode The permissions of the bus, any permission for a user implies 1139 | * full permissions for that user, except only the owner may 1140 | * edit the bus's associated file 1141 | * @return 0 on success, -1 on error 1142 | */ 1143 | int 1144 | bus_chmod(const char *file, mode_t mode) 1145 | { 1146 | bus_t bus; 1147 | mode_t fmode; 1148 | struct semid_ds sem_stat; 1149 | struct shmid_ds shm_stat; 1150 | int shm_id; 1151 | 1152 | mode = (mode & S_IRWXU) ? (mode | S_IRWXU) : (mode & (mode_t)~S_IRWXU); 1153 | mode = (mode & S_IRWXG) ? (mode | S_IRWXG) : (mode & (mode_t)~S_IRWXG); 1154 | mode = (mode & S_IRWXO) ? (mode | S_IRWXO) : (mode & (mode_t)~S_IRWXO); 1155 | mode &= (S_IWUSR | S_IWGRP | S_IWOTH | S_IRUSR | S_IRGRP | S_IROTH); 1156 | fmode = mode & (mode_t)~(S_IWGRP | S_IWOTH); 1157 | 1158 | t(bus_open(&bus, file, -1)); 1159 | t(chmod(file, fmode)); 1160 | 1161 | /* chmod sem */ 1162 | t(open_semaphores(&bus)); 1163 | t(semctl(bus.sem_id, 0, IPC_STAT, &sem_stat)); 1164 | sem_stat.sem_perm.mode = (unsigned short)mode; 1165 | t(semctl(bus.sem_id, 0, IPC_SET, &sem_stat)); 1166 | 1167 | /* chmod shm */ 1168 | t(shm_id = shmget(bus.key_shm, (size_t)BUS_MEMORY_SIZE, 0)); 1169 | t(shmctl(shm_id, IPC_STAT, &shm_stat)); 1170 | shm_stat.shm_perm.mode = (unsigned short)mode; 1171 | t(shmctl(shm_id, IPC_SET, &shm_stat)); 1172 | 1173 | return 0; 1174 | fail: 1175 | return -1; 1176 | } 1177 | --------------------------------------------------------------------------------