diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bus.c | 1197 | ||||
-rw-r--r-- | src/bus.h | 347 | ||||
-rw-r--r-- | src/cmdline.c | 429 |
3 files changed, 0 insertions, 1973 deletions
diff --git a/src/bus.c b/src/bus.c deleted file mode 100644 index e9dd789..0000000 --- a/src/bus.c +++ /dev/null @@ -1,1197 +0,0 @@ -/** - * MIT/X Consortium License - * - * Copyright © 2015 Mattias Andrée <maandree@member.fsf.org> - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#define _XOPEN_SOURCE 700 -#define _GNU_SOURCE -#include "bus.h" - -#include <stdlib.h> -#include <stdio.h> -#include <time.h> -#include <errno.h> -#include <string.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <time.h> - -#include <sys/ipc.h> -#include <sys/sem.h> -#include <sys/shm.h> - - - -#ifdef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_EVEN_HARDER -# ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER -# define BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER -# endif -#endif -#ifdef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER -# ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS -# define BUS_SEMAPHORES_ARE_SYNCHRONOUS -# endif -#endif - - - -/** - * Semaphore used to signal `bus_write` that `bus_read` is ready - */ -#define S 0 - -/** - * Semaphore for making `bus_write` wait while `bus_read` is reseting `S` - */ -#define W 1 - -/** - * Binary semaphore for making `bus_write` exclusively locked - */ -#define X 2 - -/** - * Semaphore used to cue `bus_read` that it may read the shared memory - */ -#define Q 3 - -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_EVEN_HARDER -/** - * Semaphore used to notify `bus_read` that it may restore `S` - */ -#define N 4 - -/** - * The number of semaphores in the semaphore array - */ -#define BUS_SEMAPHORES 5 -#else -#define BUS_SEMAPHORES 4 -#endif - -/** - * The default permission mits of the bus - */ -#define DEFAULT_MODE 0600 - - - -/** - * Decrease the value of a semaphore by 1 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `SEM_UNDO` if the action should be undone when the program exits, - * `IPC_NOWAIT` if the action should fail if it would block - * @return :int 0 on success, -1 on error - */ -#define acquire_semaphore(bus, semaphore, flags) \ - semaphore_op(bus, semaphore, -1, flags) - -/** - * Increase the value of a semaphore by 1 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `SEM_UNDO` if the action should be undone when the program exits - * @return :int 0 on success, -1 on error - */ -#define release_semaphore(bus, semaphore, flags) \ - semaphore_op(bus, semaphore, +1, flags) - -/** - * Wait for the value of a semaphore to become 0 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `IPC_NOWAIT` if the action should fail if it would block - * @return :int 0 on success, -1 on error - */ -#define zero_semaphore(bus, semaphore, flags) \ - semaphore_op(bus, semaphore, 0, flags) - -/** - * Decrease the value of a semaphore by 1 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `SEM_UNDO` if the action should be undone when the program exits, - * `IPC_NOWAIT` if the action should fail if it would block - * @param timeout:const struct timespec * The amount of time to wait before failing - * @return :int 0 on success, -1 on error - */ -#define acquire_semaphore_timed(bus, semaphore, flags, timeout) \ - semaphore_op_timed(bus, semaphore, -1, flags, timeout) - -/** - * Increase the value of a semaphore by 1 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `SEM_UNDO` if the action should be undone when the program exits - * @param timeout:const struct timespec * The amount of time to wait before failing - * @return :int 0 on success, -1 on error - */ -#define release_semaphore_timed(bus, semaphore, flags, timeout) \ - semaphore_op_timed(bus, semaphore, +1, flags, timeout) - -/** - * Wait for the value of a semaphore to become 0 - * - * @param bus:const bus_t * The bus - * @param semaphore:int The index of the semaphore, `S`, `W`, `X` or `Q` - * @param flags:int `IPC_NOWAIT` if the action should fail if it would block - * @param timeout:const struct timespec * The amount of time to wait before failing - * @return :int 0 on success, -1 on error - */ -#define zero_semaphore_timed(bus, semaphore, flags, timeout) \ - semaphore_op_timed(bus, semaphore, 0, flags, timeout) - -/** - * Open the semaphore array - * - * @param bus:const bus_t * The bus - * @return :int 0 on success, -1 on error - */ -#define open_semaphores(bus) \ - (((bus)->sem_id = semget((bus)->key_sem, BUS_SEMAPHORES, 0)) == -1 ? -1 : 0) - -/** - * Write a message to the shared memory - * - * @param bus:const bus_t * The bus - * @param msg:const char * The message - * @return :int 0 on success, -1 on error - */ -#define write_shared_memory(bus, msg) \ - (memcpy((bus)->message, msg, (strlen(msg) + 1) * sizeof(char))) - - -/** - * Set `delta` to the convertion of `timeout` from absolute to relative time, - * measured in the clock whose ID is specified by `clockid` - * - * @scope timeout:struct timespec Output variable for relative time - * @scope timeout:const struct timespec * The absolute time - * @scope clockid:clockid_t The clock time is measured - */ -#define DELTA \ - do { \ - if (absolute_time_to_delta_time(&delta, timeout, clockid) < 0) goto fail; \ - else if ((delta.tv_sec < 0) || (delta.tv_nsec < 0)) { errno = EAGAIN; goto fail; } \ - } while (0) - - -/** - * If `flags & (bus_flag)`, this macro evalutes to `sys_flag`, - * otherwise this macro evalutes to 0. - */ -#define F(bus_flag, sys_flag) \ - ((flags & (bus_flag)) ? sys_flag : 0) - - - -/** - * Statement wrapper that goes to `fail` on failure - */ -#define t(inst) \ - if ((inst) == -1) goto fail - - - -#ifdef _SEM_SEMUN_UNDEFINED -union semun { - int val; - struct semid_ds *buf; - unsigned short *array; -}; -#endif - - - -/** - * Create a semaphore array for the bus - * - * @param bus Bus information to fill with the key of the created semaphore array - * @return 0 on success, -1 on error - */ -static int -create_semaphores(bus_t *bus) -{ - int id = -1, rint, saved_errno; - double r; - union semun values; - - values.array = NULL; - - /* Create semaphore array. */ - for (;;) { - rint = rand(); - r = (double)rint; - r /= (double)RAND_MAX + 1; - r *= (1 << (8 * sizeof(key_t) - 2)) - 1; - bus->key_sem = (key_t)r + 1; - if (bus->key_sem == IPC_PRIVATE) - continue; - id = semget(bus->key_sem, BUS_SEMAPHORES, IPC_CREAT | IPC_EXCL | DEFAULT_MODE); - if (id != -1) - break; - if ((errno != EEXIST) && (errno != EINTR)) - goto fail; - } - - /* Initialise the array. */ - values.array = calloc((size_t)BUS_SEMAPHORES, sizeof(unsigned short)); - if (!values.array) - goto fail; - values.array[X] = 1; - if (semctl(id, 0, SETALL, values.array) == -1) - goto fail; - free(values.array); - values.array = NULL; - - return 0; - -fail: - saved_errno = errno; - if (id != -1) - semctl(id, 0, IPC_RMID); - free(values.array); - errno = saved_errno; - return -1; -} - - -/** - * Create a shared memory for the bus - * - * @param bus Bus information to fill with the key of the created shared memory - * @return 0 on success, -1 on error - */ -static int -create_shared_memory(bus_t *bus) -{ - int id = -1, rint, saved_errno; - double r; - struct shmid_ds _info; - - /* Create shared memory. */ - for (;;) { - rint = rand(); - r = (double)rint; - r /= (double)RAND_MAX + 1; - r *= (1 << (8 * sizeof(key_t) - 2)) - 1; - bus->key_shm = (key_t)r + 1; - if (bus->key_shm == IPC_PRIVATE) - continue; - id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, IPC_CREAT | IPC_EXCL | DEFAULT_MODE); - if (id != -1) - break; - if ((errno != EEXIST) && (errno != EINTR)) - goto fail; - } - - return 0; - -fail: - saved_errno = errno; - if (id != -1) - shmctl(id, IPC_RMID, &_info); - errno = saved_errno; - return -1; -} - - -/** - * Remove the semaphore array for the bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -static int -remove_semaphores(const bus_t *bus) -{ - int id = semget(bus->key_sem, BUS_SEMAPHORES, 0); - return ((id == -1) || (semctl(id, 0, IPC_RMID) == -1)) ? -1 : 0; -} - - -/** - * Remove the shared memory for the bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -static int -remove_shared_memory(const bus_t *bus) -{ - struct shmid_ds _info; - int id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, 0); - return ((id == -1) || (shmctl(id, IPC_RMID, &_info) == -1)) ? -1 : 0; -} - - -/** - * Increase or decrease the value of a semaphore, or wait the it to become 0 - * - * @param bus Bus information - * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` - * @param delta The adjustment to make to the semaphore's value, 0 to wait for it to become 0 - * @param flags `SEM_UNDO` if the action should be undone when the program exits - * @return 0 on success, -1 on error - */ -static int -semaphore_op(const bus_t *bus, int semaphore, int delta, int flags) -{ - struct sembuf op; - op.sem_num = (unsigned short)semaphore; - op.sem_op = (short)delta; - op.sem_flg = (short)flags; - return semop(bus->sem_id, &op, (size_t)1); -} - - -/** - * Increase or decrease the value of a semaphore, or wait the it to become 0 - * - * @param bus Bus information - * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` - * @param delta The adjustment to make to the semaphore's value, 0 to wait for it to become 0 - * @param flags `SEM_UNDO` if the action should be undone when the program exits - * @param timeout The amount of time to wait before failing - * @return 0 on success, -1 on error - */ -static int -semaphore_op_timed(const bus_t *bus, int semaphore, int delta, int flags, const struct timespec *timeout) -{ - struct sembuf op; - op.sem_num = (unsigned short)semaphore; - op.sem_op = (short)delta; - op.sem_flg = (short)flags; - return semtimedop(bus->sem_id, &op, (size_t)1, timeout); -} - - -/** - * Set the value of a semaphore - * - * @param bus Bus information - * @param semaphore The index of the semaphore, `S`, `W`, `X` or `Q` - * @param value The new value of the semaphore - * @return 0 on success, -1 on error - */ -static int -write_semaphore(const bus_t *bus, unsigned semaphore, int value) -{ - union semun semval; - semval.val = value; - return semctl(bus->sem_id, (unsigned short)semaphore, SETVAL, semval); -} - - -/** - * Open the shared memory for the bus - * - * @param bus Bus information - * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR` - * @return 0 on success, -1 on error - */ -static int -open_shared_memory(bus_t *bus, int flags) -{ - int id; - void *address; - t(id = shmget(bus->key_shm, (size_t)BUS_MEMORY_SIZE, 0)); - address = shmat(id, NULL, (flags & BUS_RDONLY) ? SHM_RDONLY : 0); - if ((address == (void *)-1) || !address) - goto fail; - bus->message = (char *)address; - return 0; -fail: - return -1; -} - - -/** - * Close the shared memory for the bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -static int -close_shared_memory(bus_t *bus) -{ - t(shmdt(bus->message)); - bus->message = NULL; - return 0; -fail: - return -1; -} - - -/** - * Get a random ASCII letter or digit - * - * @return A random ASCII letter or digit - */ -static char -randomchar(void) -{ - int rint = rand(); - double r = (double)rint; - r /= (double)RAND_MAX + 1; - r *= 10 + 26 + 26; - return "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"[(int)r]; -} - - -/** - * Basically, this is `mkdir -p -m $mode $pathname` - * - * @param pathname The pathname of the directory to create if missing - * @param mode The permission bits of any created directory - * @return 0 on sucess, -1 on error - */ -static int -mkdirs(char *pathname, mode_t mode) -{ - size_t i, n = strlen(pathname); - char c; - for (i = 0; i < n; i++) - if (pathname[i] != '/') - break; - for (; i < n; i++) { - if (pathname[i] == '/') { - c = pathname[i]; - if (access(pathname, F_OK)) - if (mkdir(pathname, mode) < 0) - return -1; - pathname[i] = c; - break; - } - } - if (access(pathname, F_OK)) - if (mkdir(pathname, mode) < 0) - return -1; - return 0; -} - - -/** - * Convert an absolute time to a relative time - * - * @param delta Output parameter for the relative time - * @param absolute The absolute time - * @param clockid The ID of the clock the time is measured in - * @return 0 on success, -1 on error - */ -static int -absolute_time_to_delta_time(struct timespec *delta, const struct timespec *absolute, clockid_t clockid) -{ - if (clock_gettime(clockid, delta) < 0) - return -1; - - delta->tv_sec = absolute->tv_sec - delta->tv_sec; - delta->tv_nsec = absolute->tv_nsec - delta->tv_nsec; - - if (delta->tv_nsec < 0L) { - delta->tv_nsec += 1000000000L; - delta->tv_sec -= 1; - } - if (delta->tv_nsec >= 1000000000L) { - delta->tv_nsec -= 1000000000L; - delta->tv_sec += 1; - } - - return 0; -} - - - -/** - * Create a new bus - * - * @param file The pathname of the bus, `NULL` to create a random one - * @param flags `BUS_EXCL` (if `file` is not `NULL`) to fail if the file - * already exists, otherwise if the file exists, nothing - * will happen; - * `BUS_INTR` to fail if interrupted - * @param out_file Output parameter for the pathname of the bus - * @return 0 on success, -1 on error - */ -int -bus_create(const char *restrict file, int flags, char **restrict out_file) -{ - int fd = -1, saved_errno; - bus_t bus; - char buf[1 + 2 * (3 * sizeof(ssize_t) + 2)]; - size_t ptr, len; - ssize_t wrote; - char *genfile = NULL; - const char *env; - - if (out_file) - *out_file = NULL; - - bus.sem_id = -1; - bus.key_sem = -1; - bus.key_shm = -1; - bus.message = NULL; - bus.first_poll = 0; - - srand((unsigned int)time(NULL) + (unsigned int)rand()); - - if (file) { - fd = open(file, O_WRONLY | O_CREAT | O_EXCL, DEFAULT_MODE); - if (fd == -1) { - if ((errno != EEXIST) || (flags & BUS_EXCL)) - return -1; - goto done; - } - } else { - env = getenv("XDG_RUNTIME_DIR"); - if (!env || !*env) - env = "/run"; - genfile = malloc((strlen(env) + 6 + 7 + 30) * sizeof(char)); - if (!genfile) - goto fail; - if (out_file) - *out_file = genfile; - sprintf(genfile, "%s/bus", env); - t(mkdirs(genfile, 0755)); - sprintf(genfile, "%s/bus/random.", env); - len = strlen(genfile); - genfile[len + 30] = '\0'; - retry: - for (ptr = 0; ptr < 30; ptr++) - genfile[len + ptr] = randomchar(); - fd = open(genfile, O_WRONLY | O_CREAT | O_EXCL, DEFAULT_MODE); - if (fd == -1) { - if (errno == EEXIST) - goto retry; - return -1; - } - } - - t(create_semaphores(&bus)); - t(create_shared_memory(&bus)); - - sprintf(buf, "%zi\n%zi\n", (ssize_t)(bus.key_sem), (ssize_t)(bus.key_shm)); - for (len = strlen(buf), ptr = 0; ptr < len;) { - wrote = write(fd, buf + ptr, len - ptr); - if (wrote < 0) { - if ((errno != EINTR) || (flags & BUS_INTR)) - goto fail; - } else { - ptr += (size_t)wrote; - } - } - close(fd); - -done: - if (out_file && !*out_file) { - len = strlen(file) + 1; - *out_file = malloc(len * sizeof(char)); - memcpy(*out_file, file, len * sizeof(char)); - } else if (!out_file) { - free(genfile); - } - return 0; - -fail: - saved_errno = errno; - if (bus.key_sem) - remove_semaphores(&bus); - if (bus.key_shm) - remove_shared_memory(&bus); - if (fd == -1) - close(fd); - if (out_file) - *out_file = NULL; - free(genfile); - unlink(file); - errno = saved_errno; - return -1; -} - - -/** - * Remove a bus - * - * @param file The pathname of the bus - * @return 0 on success, -1 on error - */ -int -bus_unlink(const char *file) -{ - int r = 0, saved_errno = 0; - bus_t bus; - t(bus_open(&bus, file, -1)); - - r |= remove_semaphores(&bus); - if (r && !saved_errno) - saved_errno = errno; - - r |= remove_shared_memory(&bus); - if (r && !saved_errno) - saved_errno = errno; - - r |= unlink(file); - if (r && !saved_errno) - saved_errno = errno; - - errno = saved_errno; - return r; -fail: - return -1; -} - - -/** - * Open an existing bus - * - * @param bus Bus information to fill - * @param file The filename of the bus - * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR` - * any negative value is used internally - * for telling the function to not actually - * opening the bus, but just to parse the file - * @return 0 on success, -1 on error - */ -int -bus_open(bus_t *restrict bus, const char *restrict file, int flags) -{ - int saved_errno; - char *line = NULL; - size_t len = 0; - FILE *f; - - bus->sem_id = -1; - bus->key_sem = -1; - bus->key_shm = -1; - bus->message = NULL; - - f = fopen(file, "r"); - - t(getline(&line, &len, f)); - t(bus->key_sem = (key_t)atoll(line)); - free(line), line = NULL, len = 0; - - t(getline(&line, &len, f)); - t(bus->key_shm = (key_t)atoll(line)); - free(line), line = NULL; - - fclose(f); - - if (flags >= 0) { - t(open_semaphores(bus)); - t(open_shared_memory(bus, flags)); - } - return 0; -fail: - saved_errno = errno; - free(line); - errno = saved_errno; - return -1; -} - - -/** - * Close a bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int -bus_close(bus_t *bus) -{ - bus->sem_id = -1; - if (bus->message) - t(close_shared_memory(bus)); - bus->message = NULL; - return 0; - -fail: - return -1; -} - - -/** - * Broadcast a message on a bus - * - * @param bus Bus information - * @param message The message to write, may not be longer than - * `BUS_MEMORY_SIZE` including the NUL-termination - * @param flags `BUS_NOWAIT` if this function shall fail if - * another process is currently running this - * procedure - * @return 0 on success, -1 on error - */ -int -bus_write(const bus_t *bus, const char *message, int flags) -{ - int saved_errno; -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - int state = 0; -#endif - if (acquire_semaphore(bus, X, SEM_UNDO | F(BUS_NOWAIT, IPC_NOWAIT)) == -1) - return -1; - t(zero_semaphore(bus, W, 0)); - write_shared_memory(bus, message); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - t(release_semaphore(bus, N, SEM_UNDO)); state++; -#endif - t(write_semaphore(bus, Q, 0)); - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - t(acquire_semaphore(bus, N, SEM_UNDO)); state--; -#endif - t(release_semaphore(bus, X, SEM_UNDO)); - return 0; - -fail: - saved_errno = errno; -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - if (state > 0) - acquire_semaphore(bus, N, SEM_UNDO); -#endif - release_semaphore(bus, X, SEM_UNDO); - errno = saved_errno; - return -1; -} - - -/** - * Broadcast a message on a bus - * - * @param bus Bus information - * @param message The message to write, may not be longer than - * `BUS_MEMORY_SIZE` including the NUL-termination - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return 0 on success, -1 on error - */ -int bus_write_timed(const bus_t *bus, const char *message, - const struct timespec *timeout, clockid_t clockid) -{ - int saved_errno; -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - int state = 0; -#endif - struct timespec delta; - if (!timeout) - return bus_write(bus, message, 0); - - DELTA; - if (acquire_semaphore_timed(bus, X, SEM_UNDO, &delta) == -1) - return -1; - DELTA; - t(zero_semaphore_timed(bus, W, 0, &delta)); - write_shared_memory(bus, message); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - t(release_semaphore(bus, N, SEM_UNDO)); state++; -#endif - t(write_semaphore(bus, Q, 0)); - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - t(acquire_semaphore(bus, N, SEM_UNDO)); state--; -#endif - t(release_semaphore(bus, X, SEM_UNDO)); - return 0; - -fail: - saved_errno = errno; -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS - if (state > 0) - acquire_semaphore(bus, N, SEM_UNDO); -#endif - release_semaphore(bus, X, SEM_UNDO); - errno = saved_errno; - return -1; -} - - -/** - * Listen (in a loop, forever) for new message on a bus - * - * @param bus Bus information - * @param callback Function to call when a message is received, the - * input parameters will be the read message and - * `user_data` from `bus_read`'s parameter with the - * same name. The message must have been parsed or - * copied when `callback` returns as it may be over - * overridden after that time. `callback` should - * return either of the the values: - * * 0: stop listening - * * 1: continue listening - * * -1: an error has occurred - * However, the function [`bus_read`] will invoke - * `callback` with `message` set to `NULL`one time - * directly after it has started listening on the - * bus. This is to the the program now it can safely - * continue with any action that requires that the - * programs is listening on the bus. - * @param user_data Parameter passed to `callback` - * @return 0 on success, -1 on error - */ -int -bus_read(const bus_t *restrict bus, int (*callback)(const char *message, void *user_data), void *user_data) -{ - int r, state = 0, saved_errno; - if (release_semaphore(bus, S, SEM_UNDO) == -1) - return -1; - t(r = callback(NULL, user_data)); - if (!r) goto done; - for (;;) { - t(release_semaphore(bus, Q, 0)); - t(zero_semaphore(bus, Q, 0)); - t(r = callback(bus->message, user_data)); - if (!r) goto done; - t(release_semaphore(bus, W, SEM_UNDO)); state++; - t(acquire_semaphore(bus, S, SEM_UNDO)); state++; - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER - t(zero_semaphore(bus, N, 0)); -#endif - t(release_semaphore(bus, S, SEM_UNDO)); state--; - t(acquire_semaphore(bus, W, SEM_UNDO)); state--; - } - -fail: - saved_errno = errno; - if (state > 1) - release_semaphore(bus, S, SEM_UNDO); - if (state > 0) - acquire_semaphore(bus, W, SEM_UNDO); - acquire_semaphore(bus, S, SEM_UNDO); - errno = saved_errno; - return -1; - -done: - t(acquire_semaphore(bus, S, SEM_UNDO)); - return 0; -} - - -/** - * Listen (in a loop, forever) for new message on a bus - * - * @param bus Bus information - * @param callback Function to call when a message is received, the - * input parameters will be the read message and - * `user_data` from `bus_read`'s parameter with the - * same name. The message must have been parsed or - * copied when `callback` returns as it may be over - * overridden after that time. `callback` should - * return either of the the values: - * * 0: stop listening - * * 1: continue listening - * * -1: an error has occurred - * However, the function [`bus_read`] will invoke - * `callback` with `message` set to `NULL`one time - * directly after it has started listening on the - * bus. This is to the the program now it can safely - * continue with any action that requires that the - * programs is listening on the bus. - * @param user_data Parameter passed to `callback` - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed, note that the callback - * function may or may not have been called - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return 0 on success, -1 on error - */ -int bus_read_timed(const bus_t *restrict bus, int (*callback)(const char *message, void *user_data), - void *user_data, const struct timespec *timeout, clockid_t clockid) -{ - int r, state = 0, saved_errno; - struct timespec delta; - if (!timeout) - return bus_read(bus, callback, user_data); - - DELTA; - if (release_semaphore_timed(bus, S, SEM_UNDO, &delta) == -1) - return -1; - t(r = callback(NULL, user_data)); - if (!r) goto done; - for (;;) { - DELTA; - t(release_semaphore_timed(bus, Q, 0, &delta)); - DELTA; - t(zero_semaphore_timed(bus, Q, 0, &delta)); - t(r = callback(bus->message, user_data)); - if (!r) goto done; - t(release_semaphore(bus, W, SEM_UNDO)); state++; - t(acquire_semaphore(bus, S, SEM_UNDO)); state++; - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER - t(zero_semaphore(bus, N, 0)); -#endif - t(release_semaphore(bus, S, SEM_UNDO)); state--; - t(acquire_semaphore(bus, W, SEM_UNDO)); state--; - } - -fail: - saved_errno = errno; - if (state > 1) - release_semaphore(bus, S, SEM_UNDO); - if (state > 0) - acquire_semaphore(bus, W, SEM_UNDO); - acquire_semaphore(bus, S, SEM_UNDO); - errno = saved_errno; - return -1; - -done: - t(acquire_semaphore(bus, S, SEM_UNDO)); - return 0; -} - - -/** - * Announce that the thread is listening on the bus. - * This is required so the will does not miss any - * messages due to race conditions. Additionally, - * not calling this function will cause the bus the - * misbehave, is `bus_poll` is written to expect - * this function to have been called. - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int -bus_poll_start(bus_t *bus) -{ - bus->first_poll = 1; - t(release_semaphore(bus, S, SEM_UNDO)); - t(release_semaphore(bus, Q, 0)); - return 0; - -fail: - return -1; -} - - -/** - * Announce that the thread has stopped listening on the bus. - * This is required so that the thread does not cause others - * to wait indefinitely. - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int -bus_poll_stop(const bus_t *bus) -{ - return acquire_semaphore(bus, S, SEM_UNDO | IPC_NOWAIT); -} - - -/** - * Wait for a message to be broadcasted on the bus. - * The caller should make a copy of the received message, - * without freeing the original copy, and parse it in a - * separate thread. When the new thread has started be - * started, the caller of this function should then - * either call `bus_poll` again or `bus_poll_stop`. - * - * @param bus Bus information - * @param flags `BUS_NOWAIT` if the bus should fail and set `errno` to - * `EAGAIN` if there isn't already a message available on the bus - * @return The received message, `NULL` on error - */ -const char * -bus_poll(bus_t *bus, int flags) -{ - int state = 0, saved_errno; - if (!bus->first_poll) { - t(release_semaphore(bus, W, SEM_UNDO)); state++; - t(acquire_semaphore(bus, S, SEM_UNDO)); state++; - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER - t(zero_semaphore(bus, N, 0)); -#endif - t(release_semaphore(bus, S, SEM_UNDO)); state--; - t(acquire_semaphore(bus, W, SEM_UNDO)); state--; - t(release_semaphore(bus, Q, 0)); - } else { - bus->first_poll = 0; - } - state--; - t(zero_semaphore(bus, Q, F(BUS_NOWAIT, IPC_NOWAIT))); - return bus->message; - -fail: - saved_errno = errno; - if (state > 1) - release_semaphore(bus, S, SEM_UNDO); - if (state > 0) - acquire_semaphore(bus, W, SEM_UNDO); - if (state < 0) - bus->first_poll = 1; - errno = saved_errno; - return NULL; -} - - -/** - * Wait for a message to be broadcasted on the bus. - * The caller should make a copy of the received message, - * without freeing the original copy, and parse it in a - * separate thread. When the new thread has started be - * started, the caller of this function should then - * either call `bus_poll_timed` again or `bus_poll_stop`. - * - * @param bus Bus information - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return The received message, `NULL` on error - */ -const char *bus_poll_timed(bus_t *bus, const struct timespec *timeout, clockid_t clockid) -{ - int state = 0, saved_errno; - struct timespec delta; - if (!timeout) - return bus_poll(bus, 0); - - if (!bus->first_poll) { - t(release_semaphore(bus, W, SEM_UNDO)); state++; - t(acquire_semaphore(bus, S, SEM_UNDO)); state++; - t(zero_semaphore(bus, S, 0)); -#ifndef BUS_SEMAPHORES_ARE_SYNCHRONOUS_ME_HARDER - t(zero_semaphore(bus, N, 0)); -#endif - t(release_semaphore(bus, S, SEM_UNDO)); state--; - t(acquire_semaphore(bus, W, SEM_UNDO)); state--; - t(release_semaphore(bus, Q, 0)); - } else { - bus->first_poll = 0; - } - state--; - DELTA; - t(zero_semaphore_timed(bus, Q, 0, &delta)); - return bus->message; - -fail: - saved_errno = errno; - if (state > 1) - release_semaphore(bus, S, SEM_UNDO); - if (state > 0) - acquire_semaphore(bus, W, SEM_UNDO); - if (state < 0) - bus->first_poll = 1; - errno = saved_errno; - return NULL; -} - - -/** - * Change the ownership of a bus - * - * `stat(2)` can be used of the bus's associated file to get the bus's ownership - * - * @param file The pathname of the bus - * @param owner The user ID of the bus's new owner - * @param group The group ID of the bus's new group - * @return 0 on success, -1 on error - */ -int -bus_chown(const char *file, uid_t owner, gid_t group) -{ - bus_t bus; - struct semid_ds sem_stat; - struct shmid_ds shm_stat; - int shm_id; - - t(bus_open(&bus, file, -1)); - t(chown(file, owner, group)); - - /* chown sem */ - t(open_semaphores(&bus)); - t(semctl(bus.sem_id, 0, IPC_STAT, &sem_stat)); - sem_stat.sem_perm.uid = owner; - sem_stat.sem_perm.gid = group; - t(semctl(bus.sem_id, 0, IPC_SET, &sem_stat)); - - /* chown shm */ - t(shm_id = shmget(bus.key_shm, (size_t)BUS_MEMORY_SIZE, 0)); - t(shmctl(shm_id, IPC_STAT, &shm_stat)); - shm_stat.shm_perm.uid = owner; - shm_stat.shm_perm.gid = group; - t(shmctl(shm_id, IPC_SET, &shm_stat)); - - return 0; -fail: - return -1; -} - - -/** - * Change the permissions for a bus - * - * `stat(2)` can be used of the bus's associated file to get the bus's permissions - * - * @param file The pathname of the bus - * @param mode The permissions of the bus, any permission for a user implies - * full permissions for that user, except only the owner may - * edit the bus's associated file - * @return 0 on success, -1 on error - */ -int -bus_chmod(const char *file, mode_t mode) -{ - bus_t bus; - mode_t fmode; - struct semid_ds sem_stat; - struct shmid_ds shm_stat; - int shm_id; - - mode = (mode & S_IRWXU) ? (mode | S_IRWXU) : (mode & (mode_t)~S_IRWXU); - mode = (mode & S_IRWXG) ? (mode | S_IRWXG) : (mode & (mode_t)~S_IRWXG); - mode = (mode & S_IRWXO) ? (mode | S_IRWXO) : (mode & (mode_t)~S_IRWXO); - mode &= (S_IWUSR | S_IWGRP | S_IWOTH | S_IRUSR | S_IRGRP | S_IROTH); - fmode = mode & (mode_t)~(S_IWGRP | S_IWOTH); - - t(bus_open(&bus, file, -1)); - t(chmod(file, fmode)); - - /* chmod sem */ - t(open_semaphores(&bus)); - t(semctl(bus.sem_id, 0, IPC_STAT, &sem_stat)); - sem_stat.sem_perm.mode = (unsigned short)mode; - t(semctl(bus.sem_id, 0, IPC_SET, &sem_stat)); - - /* chmod shm */ - t(shm_id = shmget(bus.key_shm, (size_t)BUS_MEMORY_SIZE, 0)); - t(shmctl(shm_id, IPC_STAT, &shm_stat)); - shm_stat.shm_perm.mode = (unsigned short)mode; - t(shmctl(shm_id, IPC_SET, &shm_stat)); - - return 0; -fail: - return -1; -} - diff --git a/src/bus.h b/src/bus.h deleted file mode 100644 index ff2f1fb..0000000 --- a/src/bus.h +++ /dev/null @@ -1,347 +0,0 @@ -/** - * MIT/X Consortium License - * - * Copyright © 2015 Mattias Andrée <maandree@member.fsf.org> - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#ifndef BUS_H -#define BUS_H - - -#ifndef _DEFAULT_SOURCE -# define _DEFAULT_SOURCE -#endif -#include <sys/types.h> -#include <time.h> - - - -#if defined(__GNUC__) -# define BUS_COMPILER_GCC(X) X -#else -# define BUS_COMPILER_GCC(X) /* ignore */ -#endif - - - -/** - * Open the bus for reading only - */ -#define BUS_RDONLY 1 - -/** - * Open the bus for writing only - */ -#define BUS_WRONLY 0 - -/** - * Open the bus for both reading and writing only - */ -#define BUS_RDWR 0 - -/** - * Fail to create bus if its file already exists - */ -#define BUS_EXCL 2 - -/** - * Fail if interrupted - */ -#define BUS_INTR 4 - -/** - * Function shall fail with errno set to `EAGAIN` - * if the it would block and this flag is used - */ -#define BUS_NOWAIT 1 - - - -/** - * The number of bytes in storeable in the shared memory, - * note that this includes the NUL-termination. - * This means that message can be at most one byte smaller. - */ -#define BUS_MEMORY_SIZE 2048 - - - -/** - * Bus information - */ -typedef struct bus -{ - /** - * The key for the semaphore array - */ - key_t key_sem; - - /** - * The key for the shared memory - */ - key_t key_shm; - - /** - * The ID of the semaphore array - */ - int sem_id; - - /** - * The address of the shared memory - */ - char *message; - - /** - * Non-zero if and only if `bus_poll` has not been - * called since the last `bus_poll_start`, or - * if `bus_poll` failed during reading - */ - int first_poll; - -} bus_t; - - - -/** - * Create a new bus - * - * @param file The pathname of the bus, `NULL` to create a random one - * @param flags `BUS_EXCL` (if `file` is not `NULL`) to fail if the file - * already exists, otherwise if the file exists, nothing - * will happen; - * `BUS_INTR` to fail if interrupted - * @param out_file Output parameter for the pathname of the bus - * @return 0 on success, -1 on error - */ -int bus_create(const char *restrict, int, char **restrict) - BUS_COMPILER_GCC(__attribute__((__warn_unused_result__))); - -/** - * Remove a bus - * - * @param file The pathname of the bus - * @return 0 on success, -1 on error - */ -int bus_unlink(const char *) - BUS_COMPILER_GCC(__attribute__((__nonnull__))); - - -/** - * Open an existing bus - * - * @param bus Bus information to fill - * @param file The filename of the bus - * @param flags `BUS_RDONLY`, `BUS_WRONLY` or `BUS_RDWR`, - * the value must not be negative - * @return 0 on success, -1 on error - */ -int bus_open(bus_t *restrict, const char *restrict, int) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Close a bus - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int bus_close(bus_t *) - BUS_COMPILER_GCC(__attribute__((__nonnull__))); - - -/** - * Broadcast a message on a bus - * - * @param bus Bus information - * @param message The message to write, may not be longer than - * `BUS_MEMORY_SIZE` including the NUL-termination - * @param flags `BUS_NOWAIT` if this function shall fail if - * another process is currently running this - * procedure - * @return 0 on success, -1 on error - */ -int bus_write(const bus_t *, const char *, int) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Broadcast a message on a bus - * - * @param bus Bus information - * @param message The message to write, may not be longer than - * `BUS_MEMORY_SIZE` including the NUL-termination - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return 0 on success, -1 on error - */ -int bus_write_timed(const bus_t *, const char *, const struct timespec *, clockid_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))); - - -/** - * Listen (in a loop, forever) for new message on a bus - * - * @param bus Bus information - * @param callback Function to call when a message is received, the - * (message, user_data) input parameters will be the read message and - * `user_data` from `bus_read`'s parameter with the - * same name. The message must have been parsed or - * copied when `callback` returns as it may be over - * overridden after that time. `callback` should - * return either of the the values: - * * 0: stop listening - * * 1: continue listening - * * -1: an error has occurred - * However, the function [`bus_read`] will invoke - * `callback` with `message` set to `NULL`one time - * directly after it has started listening on the - * bus. This is to the the program now it can safely - * continue with any action that requires that the - * programs is listening on the bus. - * @param user_data Parameter passed to `callback` - * @return 0 on success, -1 on error - */ -int bus_read(const bus_t *restrict, int (*)(const char *, void *), void *) - BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))); - -/** - * Listen (in a loop, forever) for new message on a bus - * - * @param bus Bus information - * @param callback Function to call when a message is received, the - * (message, user_data) input parameters will be the read message and - * `user_data` from `bus_read`'s parameter with the - * same name. The message must have been parsed or - * copied when `callback` returns as it may be over - * overridden after that time. `callback` should - * return either of the the values: - * * 0: stop listening - * * 1: continue listening - * * -1: an error has occurred - * However, the function [`bus_read`] will invoke - * `callback` with `message` set to `NULL`one time - * directly after it has started listening on the - * bus. This is to the the program now it can safely - * continue with any action that requires that the - * programs is listening on the bus. - * @param user_data Parameter passed to `callback` - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed, note that the callback - * function may or may not have been called - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return 0 on success, -1 on error - */ -int bus_read_timed(const bus_t *restrict, int (*)(const char *, void *), - void *, const struct timespec *, clockid_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__(1, 2), __warn_unused_result__))); - - -/** - * Announce that the thread is listening on the bus. - * This is required so the will does not miss any - * messages due to race conditions. Additionally, - * not calling this function will cause the bus the - * misbehave, is `bus_poll` is written to expect - * this function to have been called. - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int bus_poll_start(bus_t *) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Announce that the thread has stopped listening on the bus. - * This is required so that the thread does not cause others - * to wait indefinitely. - * - * @param bus Bus information - * @return 0 on success, -1 on error - */ -int bus_poll_stop(const bus_t *) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Wait for a message to be broadcasted on the bus. - * The caller should make a copy of the received message, - * without freeing the original copy, and parse it in a - * separate thread. When the new thread has started be - * started, the caller of this function should then - * either call `bus_poll` again or `bus_poll_stop`. - * - * @param bus Bus information - * @param flags `BUS_NOWAIT` if the bus should fail and set `errno` to - * `EAGAIN` if there isn't already a message available on the bus - * @return The received message, `NULL` on error - */ -const char *bus_poll(bus_t *, int) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Wait for a message to be broadcasted on the bus. - * The caller should make a copy of the received message, - * without freeing the original copy, and parse it in a - * separate thread. When the new thread has started be - * started, the caller of this function should then - * either call `bus_poll_timed` again or `bus_poll_stop`. - * - * @param bus Bus information - * @param timeout The time the operation shall fail with errno set - * to `EAGAIN` if not completed - * @param clockid The ID of the clock the `timeout` is measured with, - * it most be a predictable clock - * @return The received message, `NULL` on error - */ -const char *bus_poll_timed(bus_t *, const struct timespec *, clockid_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__(1), __warn_unused_result__))); - - -/** - * Change the ownership of a bus - * - * `stat(2)` can be used of the bus's associated file to get the bus's ownership - * - * @param file The pathname of the bus - * @param owner The user ID of the bus's new owner - * @param group The group ID of the bus's new group - * @return 0 on success, -1 on error - */ -int bus_chown(const char *, uid_t, gid_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - -/** - * Change the permissions for a bus - * - * `stat(2)` can be used of the bus's associated file to get the bus's permissions - * - * @param file The pathname of the bus - * @param mode The permissions of the bus, any permission for a user implies - * full permissions for that user, except only the owner may - * edit the bus's associated file - * @return 0 on success, -1 on error - */ -int bus_chmod(const char *, mode_t) - BUS_COMPILER_GCC(__attribute__((__nonnull__, __warn_unused_result__))); - - - -#endif - diff --git a/src/cmdline.c b/src/cmdline.c deleted file mode 100644 index 25939cb..0000000 --- a/src/cmdline.c +++ /dev/null @@ -1,429 +0,0 @@ -/** - * MIT/X Consortium License - * - * Copyright © 2015 Mattias Andrée <maandree@member.fsf.org> - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#include "bus.h" - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <grp.h> -#include <pwd.h> - - - -/** - * Statement wrapper that goes to `fail` on failure - */ -#define t(inst) if ((inst) == -1) goto fail - - - -/** - * The name of the process - */ -char *argv0; - -/** - * The command to spawn when a message is received - */ -static const char *command; - - - -/** - * Spawn a command because a message has been received - * - * @param message The received message - * @param user_data Not used - * @return 1 (continue listening) on success, -1 on error - */ -static int -spawn_continue(const char *message, void *user_data) -{ - pid_t pid; - if (!message) - return 1; - if ((pid = fork())) - return pid == -1 ? -1 : 1; - setenv("msg", message, 1); - execlp("sh", "sh", "-c", command, NULL); - perror(argv0); - exit(1); - (void) user_data; -} - - -/** - * Spawn a command because a message has been received - * - * @param message The received message - * @param user_data Not used - * @return 0 (stop listening) on success, -1 on error, or 1 if `message` is `NULL` - */ -static int -spawn_break(const char *message, void *user_data) -{ - pid_t pid; - if (!message) - return 1; - if ((pid = fork())) - return pid == -1 ? -1 : 0; - setenv("msg", message, 1); - execlp("sh", "sh", "-c", command, NULL); - perror(argv0); - exit(1); - (void) user_data; -} - - -/** - * Parse a permission string - * - * @param str The permission string - * @param andnot Output paramter for the mask of bits to remove (before applying `*or`) - * @param or Output paramter for the mask of bits to apply - * @return 0 on success, -1 on error - */ -static int -parse_mode(const char *str, mode_t *andnot, mode_t *or) -{ -#define U S_IRWXU -#define G S_IRWXG -#define O S_IRWXO - const char *s = str; - int numerical = 1; - mode_t mode = 0; - char op = '='; - mode_t bits; - - *andnot = 0; - *or = 0; - - if (!*s) - return errno = 0, -1; - - for (s = str; *s; s++) { - if (('0' > *s) || (*s > '7')) { - numerical = 0; - break; - } else { - mode = (mode << 3) | (*s & 15); - } - } - - if (numerical) { - *andnot = U | G | O; - *or = mode; - *or &= U | G | O; - *or = (*or & U) ? (*or | U) : (*or & (mode_t)~U); - *or = (*or & G) ? (*or | G) : (*or & (mode_t)~G); - *or = (*or & O) ? (*or | O) : (*or & (mode_t)~O); - return 0; - } - - for (s = str; *s; s++) { - if (strchr("+-=", *s)) { - op = *s; - } else if (strchr("ugo", *s)) { - if (*s == 'u') - bits = U; - else if (*s == 'g') - bits = G; - else - bits = O; - if (op == '+') { - *andnot |= bits; - *or |= bits; - } - else if (op == '-') { - *andnot |= bits; - *or &= ~bits; - } - else if (op == '=') { - *andnot |= U | G | O; - *or |= bits; - } - } else { - return errno = 0, -1; - } - } - - return 0; -} - - -/** - * Parse a user name/identifier string - * - * @param str The user's name or identifier - * @param uid Output parameter for the user's identifier - * @return 0 on success, -1 on error - */ -static int -parse_uid(const char *str, uid_t *uid) -{ - const char *s = str; - int numerical = 1; - uid_t rc = 0; - struct passwd *pwd; - - if (!*s || (*s == ':')) - return errno = 0, -1; - - for (s = str; *s; s++) { - if (('0' > *s) || (*s > '9')) { - numerical = 0; - break; - } - } - - if (numerical) { - for (s = str; *s; s++) - rc = (rc * 10) + (*s & 15); - *uid = rc; - return 0; - } - - pwd = getpwnam(str); - if (!pwd) { - return -1; - } - *uid = pwd->pw_uid; - return 0; -} - - -/** - * Parse a group name/identifier string - * - * @param str The group's name or identifier - * @param gid Output parameter for the group's identifier - * @return 0 on success, -1 on error - */ -static int -parse_gid(const char *str, gid_t *gid) -{ - const char *s = str; - int numerical = 1; - gid_t rc = 0; - struct group *grp; - - if (!*s || strchr(s, ':')) - return errno = 0, -1; - - for (s = str; *s; s++) { - if (('0' > *s) || (*s > '9')) { - numerical = 0; - break; - } - } - - if (numerical) { - for (s = str; *s; s++) - rc = (rc * 10) + (*s & 15); - *gid = rc; - return 0; - } - - grp = getgrnam(str); - if (!grp) - return -1; - *gid = grp->gr_gid; - return 0; -} - - -/** - * Parse a ownership string - * - * @param str The ownership string - * @param uid Output parameter for the owner, `NULL` if `str` only contains the group - * @param gid Output parameter for the group, `NULL` if `str` only contains the owner - * @return 0 on success, -1 on error - */ -static int -parse_owner(char *str, uid_t *uid, gid_t *gid) -{ - int r = 0; - char* group; - - if (!uid) - return parse_gid(str, gid); - if (!gid) - return parse_uid(str, uid); - - group = strchr(str, ':'); - *group++ = 0; - - r = parse_gid(group, gid); - if (r) - return r; - return parse_uid(str, uid); -} - - - -/** - * Main function of the command line interface for the bus system - * - * @param argc The number of elements in `argv` - * @param argv The command. Valid commands: - * <argv0> create [-x] [--] [<path>] # create a bus - * <argv0> remove [--] <path> # remove a bus - * <argv0> listen [--] <path> <command> # listen for new messages - * <argv0> wait [--] <path> <command> # listen for one new message - * <argv0> broadcast [-n] [--] <path> <message> # broadcast a message - * <argv0> chmod [--] <mode> <path> # change permissions - * <argv0> chown [--] <owner>[:<group>] <path> # change ownership - * <argv0> chgrp [--] <group> <path> # change group - * <command> will be spawned with $arg set to the message - * @return 0 on sucess, 1 on error, 2 on invalid command - */ -int -main(int argc, char *argv[]) -{ - bus_t bus; - char *file; - struct stat attr; - uid_t uid; - gid_t gid; - mode_t mode_andnot, mode_or; - int opt_x = 0, opt_n = 0; - const char *arg; - char **nonoptv = alloca((size_t)argc * sizeof(char*)); - int nonoptc = 0; - - argv0 = *argv++; - argc--; - - /* Parse arguments. */ - while (argc) { - if (!strcmp(*argv, "--")) { - argv++; - argc--; - break; - } else if (**argv == '-') { - arg = *argv++; - argc--; - for (arg++; *arg; arg++) { - if (*arg == 'x') - opt_x = 1; - else if (*arg == 'n') - opt_n = 1; - else - return -2; - } - } else { - *nonoptv++ = *argv++; - nonoptc++; - argc--; - } - } - while (argc) { - *nonoptv++ = *argv++; - nonoptc++; - argc--; - } - nonoptv -= nonoptc; - - /* Check options. */ - if (opt_x && strcmp(nonoptv[0], "create") && (nonoptc != 2)) - return 2; - if (opt_n && strcmp(nonoptv[0], "broadcast") && (nonoptc != 3)) - return 2; - - /* Create a new bus with selected name. */ - if ((nonoptc == 2) && !strcmp(nonoptv[0], "create")) { - t(bus_create(nonoptv[1], opt_x * BUS_EXCL, NULL)); - - /* Create a new bus with random name. */ - } else if ((nonoptc == 1) && !strcmp(nonoptv[0], "create")) { - t(bus_create(NULL, 0, &file)); - printf("%s\n", file); - free(file); - - /* Remove a bus. */ - } else if ((nonoptc == 2) && !strcmp(nonoptv[0], "remove")) { - t(bus_unlink(nonoptv[1])); - - /* Listen on a bus in a loop. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "listen")) { - command = nonoptv[2]; - t(bus_open(&bus, nonoptv[1], BUS_RDONLY)); - t(bus_read(&bus, spawn_continue, NULL)); - t(bus_close(&bus)); - - /* Listen on a bus for one message. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "wait")) { - command = nonoptv[2]; - t(bus_open(&bus, nonoptv[1], BUS_RDONLY)); - t(bus_read(&bus, spawn_break, NULL)); - t(bus_close(&bus)); - - /* Broadcast a message on a bus. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "broadcast")) { - t(bus_open(&bus, nonoptv[1], BUS_WRONLY)); - t(bus_write(&bus, nonoptv[2], opt_n * BUS_NOWAIT)); - t(bus_close(&bus)); - - /* Change permissions. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "chmod")) { - t(parse_mode(nonoptv[1], &mode_andnot, &mode_or)); - t(stat(nonoptv[2], &attr)); - attr.st_mode &= ~mode_andnot; - attr.st_mode |= mode_or; - t(bus_chmod(nonoptv[2], attr.st_mode)); - - /* Change ownership. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "chown")) { - if (strchr(nonoptv[1], ':')) { - t(parse_owner(nonoptv[1], &uid, &gid)); - t(bus_chown(nonoptv[2], uid, gid)); - } else { - t(parse_owner(nonoptv[1], &uid, NULL)); - t(stat(nonoptv[2], &attr)); - t(bus_chown(nonoptv[2], uid, attr.st_gid)); - } - - /* Change group. */ - } else if ((nonoptc == 3) && !strcmp(nonoptv[0], "chgrp")) { - t(parse_owner(nonoptv[1], NULL, &gid)); - t(stat(nonoptv[2], &attr)); - t(bus_chown(nonoptv[2], attr.st_uid, gid)); - - } else - return 2; - - return 0; - -fail: - if (errno == 0) - return 2; - perror(argv0); - return 1; -} - |