commit 3baa2617f8fa715685929c415068ab226626a2a3 from: Omar Polo date: Wed Feb 16 18:28:23 2022 UTC initial commit commit - /dev/null commit + 3baa2617f8fa715685929c415068ab226626a2a3 blob - /dev/null blob + a8da1ad97ac240b0df0d7be7c216417cd8aa6ba8 (mode 644) --- /dev/null +++ LICENSE @@ -0,0 +1,15 @@ +Copyright 2022 Omar Polo + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted, provided that the +above copyright notice and this permission notice appear in all +copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. blob - /dev/null blob + f696ec5349ed952a8c3b4c52ee353be0523a0114 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,47 @@ +PROG= amused +SRCS= amused.c control.c log.c xmalloc.c player.c ctl.c playlist.c \ + player_flac.c player_mad.c player_opus.c player_oggvorbis.c + +.include "amused-version.mk" + +CPPFLAGS += -DAMUSED_VERSION=\"${AMUSED_VERSION}\" \ + -I/usr/local/include -I/usr/local/include/opus + +LDADD = -levent -lm -lsndio -lutil \ + -L/usr/local/lib -lmad -lvorbisfile -lopusfile -lFLAC +DPADD = ${LIBEVENT} ${LIBM} ${LIBSNDIO} ${LIBUTIL} + +.if "${AMUSED_RELEASE}" == "Yes" +PREFIX ?= /usr/local +BINDIR ?= ${PREFIX}/bin +MANDIR ?= ${PREFIX}/man/man +.else +CFLAGS += -Werror -Wall -Wstrict-prototypes -Wunused-variable +PREFIX ?= ${HOME} +BINDIR ?= ${PREFIX}/bin +BINOWN ?= ${USER} +.if !defined(BINGRP) +BINGRP != id -g -n +.endif +DEBUG = -O0 -g +.endif + +release: clean + sed -i -e 's/_RELEASE=No/_RELEASE=Yes/' amused-version.mk + ${MAKE} dist + sed -i -e 's/_RELEASE=Yes/_RELEASE=No/' amused-version.mk + +dist: clean + mkdir /tmp/amused-${AMUSED_VERSION} + pax -rw * /tmp/amused-${AMUSED_VERSION} + find /tmp/amused-${AMUSED_VERSION} -type d -name obj -delete + rm /tmp/amused-${AMUSED_VERSION}/amused-dist.txt + tar -C /tmp -zcf amused-${AMUSED_VERSION}.tar.gz amused-${AMUSED_VERSION} + rm -rf /tmp/amused-${AMUSED_VERSION} + tar -ztf amused-${AMUSED_VERSION}.tar.gz | \ + sed -e 's/^amused-${AMUSED_VERSION}//' | \ + sort > amused-dist.txt.new + diff -u amused-dist.txt{,new} + rm amused-dist.txt.new + +.include blob - /dev/null blob + ae9b2a9dc2825c6e0d4b56d4a0a489641f4e7a56 (mode 644) --- /dev/null +++ amused-version.mk @@ -0,0 +1,8 @@ +AMUSED_RELEASE=No +AMUSED_VERSION_NUMBER=0.1 + +.if ${AMUSED_RELEASE} == Yes +AMUSED_VERSION=${AMUSED_VERSION_NUMBER} +.else +AMUSED_VERSION=${AMUSED_VERSION_NUMBER}-current +.endif blob - /dev/null blob + 25d20a6822f5854ddc669f139ae4496bd5919d69 (mode 644) --- /dev/null +++ amused.1 @@ -0,0 +1,82 @@ +.\" Copyright (c) 2022 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd February 16 2022 +.Dt AMUSED 1 +.Os +.Sh NAME +.Nm amused +.Nd music player +.Sh SYNOPSIS +.Nm +.Op Fl dv +.Op Fl s Ar socket +.Ar command +.Op Ar argument ... +.Sh DESCRIPTION +.Nm +is a music player daemon and command-line utility. +.Pp +If called without any sub- +.Ar command +it forks in the background and starts the daemon. +Once the daemon is running, music files can be enqueued and the reproduction +controlled. +.Pp +The following options are available: +.Bl -tag -width 12m +.It Fl d +Do not daemonize. +if this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl v +Produce more verbose output. +.It Fl s Ar socket +Use +.Ar socket +instead of the default +.Pa /tmp/amused-$UID +to communicate with the daemon. +.El +.Pp +The following commands are available: +.Bl -tag -width 12m +.It Cm play +Start playing the playlist. +.It Cm pause +Pause the reproduction. +.It Cm toggle +Toggle pause state. +.It Cm stop +Stop the music player. +.It Cm restart +Restart the current song from the beginning. +.It Cm add Ar +Enqueue the given files. +.It Cm flush +Erase the playlist. +.El +.Sh FILES +.Bl -tag -width "/tmp/amused-$UID" -compact +.It Pa /tmp/amused-$UID +UNIX-domain socket used for communication with the daemon. +.El +.Sh AUTHORS +.An -nosplit +Then +.Nm +utility was written by +.An Omar Polo Aq Mt op@openbsd.org . blob - /dev/null blob + fb8aab8c435d364c7c7a4bf64e6919e1d2bb6b71 (mode 644) --- /dev/null +++ amused.c @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "amused.h" +#include "control.h" +#include "log.h" +#include "playlist.h" +#include "xmalloc.h" + +struct sio_hdl *hdl; +char *csock = NULL; +int debug; +int verbose; +struct imsgev *iev_player; + +const char *argv0; +pid_t player_pid; +struct event ev_sigint; +struct event ev_sigterm; + +enum amused_process { + PROC_MAIN, + PROC_PLAYER, +}; + +__dead void +main_shutdown(void) +{ + pid_t pid; + int status; + + /* close pipes. */ + msgbuf_clear(&iev_player->ibuf.w); + close(iev_player->ibuf.fd); + free(iev_player); + + log_debug("waiting for children to terminate"); + do { + pid = wait(&status); + if (pid == -1) { + if (errno != EINTR && errno != ECHILD) + fatal("wait"); + } else if (WIFSIGNALED(status)) + log_warnx("player terminated; signal %d", + WTERMSIG(status)); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + log_info("terminating"); + exit(0); +} + +static void +main_sig_handler(int sig, short event, void *arg) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGTERM: + case SIGINT: + main_shutdown(); + break; + default: + fatalx("unexpected signal %d", sig); + } +} + +static void +main_dispatch_player(int sig, short event, void *d) +{ + struct imsgev *iev = d; + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg imsg; + ssize_t n; + int shut = 0; + const char *song; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_EOF: + case IMSG_ERR: + song = playlist_advance(); + if (song == NULL) + break; + /* XXX: watch out for failures! */ + main_play_song(song); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +static pid_t +start_child(enum amused_process proc, int fd) +{ + const char *argv[5]; + int argc = 0; + pid_t pid; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + case 0: + break; + default: + close(fd); + return pid; + } + + if (fd != 3) { + if (dup2(fd, 3) == -1) + fatal("cannot setup imsg fd"); + } else if (fcntl(F_SETFD, 0) == -1) + fatal("cannot setup imsg fd"); + + argv[argc++] = argv0; + switch (proc) { + case PROC_MAIN: + fatal("can not start main process"); + case PROC_PLAYER: + argv[argc++] = "-Tp"; + break; + } + + if (debug) + argv[argc++] = "-d"; + if (verbose) + argv[argc++] = "-v"; + argv[argc++] = NULL; + + /* obnoxious casts */ + execvp(argv0, (char *const *)argv); + fatal("execvp %s", argv0); +} + +/* daemon main routine */ +static __dead int +amused_main(void) +{ + int pipe_main2player[2]; + int control_fd; + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + log_procinit("main"); + + if (!debug) + daemon(1, 0); + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_main2player) == -1) + fatal("socketpair"); + + player_pid = start_child(PROC_PLAYER, pipe_main2player[1]); + + event_init(); + + signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); + + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + + signal(SIGHUP, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + iev_player = xmalloc(sizeof(*iev_player)); + imsg_init(&iev_player->ibuf, pipe_main2player[0]); + iev_player->handler = main_dispatch_player; + iev_player->events = EV_READ; + event_set(&iev_player->ev, iev_player->ibuf.fd, iev_player->events, + iev_player->handler, iev_player); + event_add(&iev_player->ev, NULL); + + if ((control_fd = control_init(csock)) == -1) + fatal("control socket setup failed %s", csock); + control_listen(control_fd); + + if (pledge("stdio rpath unix sendfd", NULL) == -1) + fatal("pledge"); + + log_info("startup"); + event_dispatch(); + main_shutdown(); +} + +int +main(int argc, char **argv) +{ + int ch, proc = PROC_MAIN; + + log_init(1, LOG_DAEMON); /* Log to stderr until daemonized */ + log_setverbose(1); + + argv0 = argv[0]; + if (argv0 == NULL) + argv0 = "amused"; + + while ((ch = getopt(argc, argv, "ds:T:vV")) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case 's': + free(csock); + csock = xstrdup(optarg); + break; + case 'T': + switch (*optarg) { + case 'p': + proc = PROC_PLAYER; + break; + default: + usage(); + } + break; + case 'v': + verbose++; + break; + case 'V': + printf("%s version %s\n", getprogname(), + AMUSED_VERSION); + exit(0); + default: + usage(); + } + } + argv += optind; + argc -= optind; + + if (proc == PROC_PLAYER) + exit(player(debug, verbose)); + + if (csock == NULL) + xasprintf(&csock, "/tmp/amused-%d", getuid()); + + if (argc == 0) + amused_main(); + else + ctl(argc, argv); + return 0; +} + +void +imsg_event_add(struct imsgev *iev) +{ + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, + pid_t pid, int fd, const void *data, uint16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, + datalen)) != -1) + imsg_event_add(iev); + + return ret; +} + +int +main_play_song(const char *song) +{ + char path[PATH_MAX] = { 0 }; + int fd; + + strlcpy(path, song, sizeof(path)); + if ((fd = open(path, O_RDONLY)) == -1) { +#if todo + log_warn("open %s", path); + return -1; +#else + fatal("open %s", path); +#endif + } + + imsg_compose_event(iev_player, IMSG_PLAY, 0, 0, fd, + path, sizeof(path)); + return 0; +} + +void +main_enqueue(struct imsgev *iev, struct imsg *imsg) +{ + size_t datalen; + char path[PATH_MAX] = { 0 }; + const char *err = NULL; + + datalen = IMSG_DATA_SIZE(*imsg); + if (datalen != sizeof(path)) { + err = "data size mismatch"; + goto err; + } + + memcpy(path, imsg->data, sizeof(path)); + if (path[datalen-1] != '\0') { + err = "malformed data"; + goto err; + } + + playlist_enqueue(path); + imsg_compose_event(iev, IMSG_CTL_ADD, 0, 0, -1, path, sizeof(path)); + return; +err: + imsg_compose_event(iev, IMSG_CTL_ERR, 0, 0, -1, err, strlen(err)+1); +} blob - /dev/null blob + 5d76e295c518c59a068d76bdf4f4155a26ef40d9 (mode 644) --- /dev/null +++ amused.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef AMUSED_H +#define AMUSED_H + +extern struct sio_hdl *hdl; +extern char *csock; +extern int debug; +extern int verbose; +extern int playing; +extern struct imsgev *iev_player; + +#define IMSG_DATA_SIZE(imsg) ((imsg).hdr.len - IMSG_HEADER_SIZE) + +enum imsg_type { + IMSG_PLAY, /* fd + filename */ + IMSG_RESUME, + IMSG_PAUSE, + IMSG_STOP, + IMSG_EOF, + IMSG_ERR, + + IMSG_CTL_PLAY, + IMSG_CTL_TOGGLE_PLAY, + IMSG_CTL_PAUSE, + IMSG_CTL_STOP, + IMSG_CTL_RESTART, + IMSG_CTL_ADD, + IMSG_CTL_FLUSH, + IMSG_CTL_SHOW, + + IMSG_CTL_ERR, +}; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + short events; +}; + +enum actions { + NONE, + PLAY, + PAUSE, + TOGGLE, + STOP, + RESTART, + ADD, + FLUSH, + SHOW, +}; + +struct ctl_command; + +struct parse_result { + enum actions action; + char **files; + struct ctl_command *ctl; +}; + +struct ctl_command { + const char *name; + enum actions action; + int (*main)(struct parse_result *, int, char **); + const char *usage; + int has_pledge; +}; + +/* amused.c */ +void imsg_event_add(struct imsgev *iev); +int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, + pid_t, int, const void *, uint16_t); +int main_play_song(const char *); +void main_enqueue(struct imsgev *, struct imsg *); + +/* ctl.c */ +__dead void usage(void); +__dead void ctl(int, char **); + +/* player.c */ +int player_setrate(int); +void player_senderr(void); +void player_sendeof(void); +int player_shouldstop(void); +int player(int, int); + +void play_oggvorbis(int fd); +void play_mp3(int fd); +void play_flac(int fd); +void play_opus(int fd); + +#endif blob - /dev/null blob + e24d6d2b6d90d0cd9c303969532e5eafc0c71fe5 (mode 644) --- /dev/null +++ control.c @@ -0,0 +1,337 @@ +/* $OpenBSD: control.c,v 1.8 2021/03/02 04:10:07 jsg Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "amused.h" +#include "log.h" +#include "control.h" +#include "playlist.h" + +#define CONTROL_BACKLOG 5 + +struct { + struct event ev; + struct event evt; + int fd; +} control_state = {.fd = -1}; + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + struct imsgev iev; +}; + +TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns); + +struct ctl_conn *control_connbyfd(int); +struct ctl_conn *control_connbypid(pid_t); +void control_close(int); + +int +control_init(char *path) +{ + struct sockaddr_un sun; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if (unlink(path) == -1) + if (errno != ENOENT) { + log_warn("%s: unlink %s", __func__, path); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("%s: bind: %s", __func__, path); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("%s: chmod", __func__); + close(fd); + (void)unlink(path); + return (-1); + } + + return (fd); +} + +int +control_listen(int fd) +{ + if (control_state.fd != -1) + fatalx("%s: received unexpected controlsock", __func__); + + control_state.fd = fd; + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) { + log_warn("%s: listen", __func__); + return (-1); + } + + event_set(&control_state.ev, control_state.fd, EV_READ, + control_accept, NULL); + event_add(&control_state.ev, NULL); + evtimer_set(&control_state.evt, control_accept, NULL); + + return (0); +} + +void +control_accept(int listenfd, short event, void *bula) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + + event_add(&control_state.ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + len = sizeof(sun); + if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len, + SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) { + struct timeval evtpause = { 1, 0 }; + + event_del(&control_state.ev); + evtimer_add(&control_state.evt, &evtpause); + } else if (errno != EWOULDBLOCK && errno != EINTR && + errno != ECONNABORTED) + log_warn("%s: accept4", __func__); + return; + } + + if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { + log_warn("%s: calloc", __func__); + close(connfd); + return; + } + + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler = control_dispatch_imsg; + c->iev.events = EV_READ; + event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, + c->iev.handler, &c->iev); + event_add(&c->iev.ev, NULL); + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.fd == fd) + break; + } + + return (c); +} + +struct ctl_conn * +control_connbypid(pid_t pid) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.pid == pid) + break; + } + + return (c); +} + +void +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + + /* Some file descriptors are available again. */ + if (evtimer_pending(&control_state.evt, NULL)) { + evtimer_del(&control_state.evt); + event_add(&control_state.ev, NULL); + } + + free(c); +} + +void +control_dispatch_imsg(int fd, short event, void *bula) +{ + struct ctl_conn *c; + struct imsg imsg; + ssize_t n; + const char *song; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + if (event & EV_READ) { + if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || + n == 0) { + control_close(fd); + return; + } + } + if (event & EV_WRITE) { + if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { + control_close(fd); + return; + } + } + + for (;;) { + if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { + control_close(fd); + return; + } + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_PLAY: + switch (play_state) { + case STATE_STOPPED: + song = playlist_advance(); + if (song == NULL) + break; + /* XXX: watch out for failures! */ + main_play_song(song); + break; + case STATE_PLAYING: + /* do nothing */ + break; + case STATE_PAUSED: + imsg_compose_event(iev_player, IMSG_RESUME, + 0, 0, -1, NULL, 0); + break; + } + break; + case IMSG_CTL_TOGGLE_PLAY: + switch (play_state) { + case STATE_STOPPED: + song = playlist_advance(); + if (song == NULL) + break; + /* XXX: watch out for failures! */ + main_play_song(song); + break; + case STATE_PLAYING: + imsg_compose_event(iev_player, IMSG_PAUSE, 0, 0, -1, + NULL, 0); + break; + case STATE_PAUSED: + imsg_compose_event(iev_player, IMSG_RESUME, 0, 0, -1, + NULL, 0); + break; + } + break; + case IMSG_CTL_PAUSE: + if (play_state != STATE_PLAYING) + break; + imsg_compose_event(iev_player, IMSG_PAUSE, 0, 0, -1, NULL, 0); + break; + case IMSG_CTL_STOP: + if (play_state == STATE_STOPPED) + break; + imsg_compose_event(iev_player, IMSG_STOP, 0, 0, -1, NULL, 0); + break; + case IMSG_CTL_RESTART: + imsg_compose_event(iev_player, IMSG_STOP, 0, 0, -1, NULL, 0); + song = playlist_current(); + if (song == NULL) + break; + /* XXX: watch out for failures */ + main_play_song(song); + break; + case IMSG_CTL_ADD: + main_enqueue(&c->iev, &imsg); + break; + case IMSG_CTL_FLUSH: + playlist_truncate(); + case IMSG_CTL_SHOW: + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->iev); +} + +int +control_imsg_relay(struct imsg *imsg) +{ + struct ctl_conn *c; + + if ((c = control_connbypid(imsg->hdr.pid)) == NULL) + return (0); + + return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid, + -1, imsg->data, IMSG_DATA_SIZE(*imsg))); +} blob - /dev/null blob + e75b3c1d5a53c935ef71364bbaabaa5c054b9acd (mode 644) --- /dev/null +++ control.h @@ -0,0 +1,23 @@ +/* $OpenBSD: control.h,v 1.3 2021/01/19 16:54:00 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +int control_init(char *); +int control_listen(int fd); +void control_accept(int, short, void *); +void control_dispatch_imsg(int, short, void *); +int control_imsg_relay(struct imsg *); blob - /dev/null blob + 786a76debc3c4310400ca69fa83a257edc3ddddf (mode 644) --- /dev/null +++ ctl.c @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "amused.h" +#include "log.h" +#include "xmalloc.h" + +static struct imsgbuf *ibuf; + +int ctl_noarg(struct parse_result *, int, char **); +int ctl_add(struct parse_result *, int, char **); + +struct ctl_command ctl_commands[] = { + { "play", PLAY, ctl_noarg, "" }, + { "pause", PAUSE, ctl_noarg, "" }, + { "toggle", TOGGLE, ctl_noarg, "" }, + { "stop", STOP, ctl_noarg, "" }, + { "restart", RESTART, ctl_noarg, "" }, + { "add", ADD, ctl_add, "files...", 1 }, + { "flush", FLUSH, ctl_noarg, "" }, + { NULL }, +}; + +__dead void +usage(void) +{ + fprintf(stderr, "usage: %s [-dv] [-s socket]\n", getprogname()); + fprintf(stderr, "%s version %s\n", getprogname(), AMUSED_VERSION); + exit(1); +} + +static __dead void +ctl_usage(struct ctl_command *ctl) +{ + fprintf(stderr, "usage: %s [-v] [-s socket] %s %s\n", getprogname(), + ctl->name, ctl->usage); + exit(1); +} + +static int +parse(int argc, char **argv) +{ + struct ctl_command *ctl = NULL; + struct parse_result res; + int i, status; + + memset(&res, 0, sizeof(res)); + + for (i = 0; ctl_commands[i].name != NULL; ++i) { + if (strncmp(ctl_commands[i].name, argv[0], strlen(argv[0])) + == 0) { + if (ctl != NULL) { + fprintf(stderr, + "ambiguous argument: %s\n", argv[0]); + usage(); + } + ctl = &ctl_commands[i]; + } + } + + if (ctl == NULL) { + fprintf(stderr, "unknown argument: %s\n", argv[0]); + usage(); + } + + res.action = ctl->action; + res.ctl = ctl; + + if (!ctl->has_pledge) { + /* pledge(2) default if command doesn't have its own */ + if (pledge("stdio", NULL) == -1) + fatal("pledge"); + } + + status = ctl->main(&res, argc, argv); + close(ibuf->fd); + free(ibuf); + return status; +} + +static int +enqueue_tracks(char **files) +{ + char res[PATH_MAX]; + int enq = 0; + + for (; *files != NULL; ++files) { + memset(&res, 0, sizeof(res)); + if (realpath(*files, res) == NULL) { + log_warn("realpath %s", *files); + continue; + } + + imsg_compose(ibuf, IMSG_CTL_ADD, 0, 0, -1, + res, sizeof(res)); + enq++; + } + + return enq == 0; +} + +static int +show_complete(struct imsg *imsg, int *ret) +{ + size_t datalen; + char path[PATH_MAX]; + + datalen = IMSG_DATA_SIZE(*imsg); + if (datalen == 0) + return 1; + + if (datalen != sizeof(path)) + fatalx("%s: data size mismatch", __func__); + memcpy(path, imsg->data, sizeof(path)); + if (path[datalen-1] != '\0') + fatalx("%s: data corrupted?", __func__); + + printf("%s\n", path); + return 0; +} + +static int +ctlaction(struct parse_result *res) +{ + struct imsg imsg; + ssize_t n; + int ret = 0, done = 1; + + switch (res->action) { + case PLAY: + imsg_compose(ibuf, IMSG_CTL_PLAY, 0, 0, -1, NULL, 0); + break; + case PAUSE: + imsg_compose(ibuf, IMSG_CTL_PAUSE, 0, 0, -1, NULL, 0); + break; + case TOGGLE: + imsg_compose(ibuf, IMSG_CTL_TOGGLE_PLAY, 0, 0, -1, NULL, 0); + break; + case STOP: + imsg_compose(ibuf, IMSG_CTL_STOP, 0, 0, -1, NULL, 0); + break; + case RESTART: + imsg_compose(ibuf, IMSG_CTL_RESTART, 0, 0, -1, NULL, 0); + break; + case ADD: + ret = enqueue_tracks(res->files); + break; + case FLUSH: + imsg_compose(ibuf, IMSG_CTL_FLUSH, 0, 0, -1, NULL, 0); + break; + case SHOW: + done = 0; + imsg_compose(ibuf, IMSG_CTL_SHOW, 0, 0, -1, NULL, 0); + break; + case NONE: + /* action not expected */ + fatalx("invalid action %u", res->action); + break; + } + + if (ret != 0) + goto end; + + imsg_flush(ibuf); + + while (!done) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatalx("imsg_read error"); + if (n == 0) + fatalx("pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatalx("imsg_get error"); + if (n == 0) + break; + + switch (res->action) { + case SHOW: + done = show_complete(&imsg, &ret); + break; + default: + done = 1; + break; + } + + imsg_free(&imsg); + } + } + +end: + return ret; +} + +int +ctl_noarg(struct parse_result *res, int argc, char **argv) +{ + if (argc != 1) + ctl_usage(res->ctl); + return ctlaction(res); +} + +int +ctl_add(struct parse_result *res, int argc, char **argv) +{ + if (argc < 2) + ctl_usage(res->ctl); + res->files = argv+1; + return ctlaction(res); +} + +__dead void +ctl(int argc, char **argv) +{ + struct sockaddr_un sun; + int ctl_sock; + + log_init(1, LOG_DAEMON); + log_setverbose(verbose); + + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + fatal("socket"); + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, csock, sizeof(sun.sun_path)); + + if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) + fatal("connect %s", csock); + + ibuf = xmalloc(sizeof(*ibuf)); + imsg_init(ibuf, ctl_sock); + + optreset = 1; + optind = 1; + + exit(parse(argc, argv)); +} blob - /dev/null blob + 50b0a0e95ac84837dc120d024ba12c1206a04f4e (mode 644) --- /dev/null +++ log.c @@ -0,0 +1,199 @@ +/* $OpenBSD: log.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" + +static int debug; +static int verbose; +static const char *log_procname; + +void +log_init(int n_debug, int facility) +{ + extern char *__progname; + + debug = n_debug; + verbose = n_debug; + log_procinit(__progname); + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, facility); + + tzset(); +} + +void +log_procinit(const char *procname) +{ + if (procname != NULL) + log_procname = procname; +} + +void +log_setverbose(int v) +{ + verbose = v; +} + +int +log_getverbose(void) +{ + return (verbose); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + int saved_errno = errno; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); + + errno = saved_errno; +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + int saved_errno = errno; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_ERR, "%s", strerror(saved_errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, + strerror(saved_errno)) == -1) { + /* we tried it... */ + vlog(LOG_ERR, emsg, ap); + logit(LOG_ERR, "%s", strerror(saved_errno)); + } else { + vlog(LOG_ERR, nfmt, ap); + free(nfmt); + } + va_end(ap); + } + + errno = saved_errno; +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_ERR, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +static void +vfatalc(int code, const char *emsg, va_list ap) +{ + static char s[BUFSIZ]; + const char *sep; + + if (emsg != NULL) { + (void)vsnprintf(s, sizeof(s), emsg, ap); + sep = ": "; + } else { + s[0] = '\0'; + sep = ""; + } + if (code) + logit(LOG_CRIT, "fatal in %s: %s%s%s", + log_procname, s, sep, strerror(code)); + else + logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s); +} + +void +fatal(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(errno, emsg, ap); + va_end(ap); + exit(1); +} + +void +fatalx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(0, emsg, ap); + va_end(ap); + exit(1); +} blob - /dev/null blob + 0fa046fc3afbe8c893d40f492481a4d3055dd2bc (mode 644) --- /dev/null +++ log.h @@ -0,0 +1,45 @@ +/* $OpenBSD: log.h,v 1.2 2021/12/13 18:28:40 deraadt Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LOG_H +#define LOG_H + +#include + +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +#endif /* LOG_H */ blob - /dev/null blob + ddc342051acb4a5567bd9e2f630bfc28d0c4e9d0 (mode 644) --- /dev/null +++ player.c @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "amused.h" +#include "log.h" +#include "xmalloc.h" + +static struct imsgbuf *ibuf; + +static int nextfd = -1; +static char nextpath[PATH_MAX]; + +volatile sig_atomic_t halted; + +static void +player_signal_handler(int signo) +{ + halted = 1; +} + +static void +audio_init(void) +{ + struct sio_par par; + + if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0)) == NULL) + fatal("sio_open"); + + sio_initpar(&par); + par.bits = 16; + par.appbufsz = 50 * par.rate / 1000; + par.pchan = 2; + if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) + fatal("couldn't set audio params"); + if (par.bits != 16 || par.le != SIO_LE_NATIVE) + fatalx("unsupported audio params"); + if (!sio_start(hdl)) + fatal("sio_start"); +} + +int +player_setrate(int rate) +{ + struct sio_par par; + + log_debug("switching to sample rate %d", rate); + + sio_stop(hdl); + + sio_initpar(&par); + par.rate = rate; + if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) { + log_warnx("invalid params"); + return -1; + } + + /* TODO: check the sample rate? */ + + if (!sio_start(hdl)) { + log_warn("sio_start"); + return -1; + } + return 0; +} + +int +player_pendingimsg(void) +{ + struct pollfd pfd; + int r; + + if (halted != 0) + return 1; + + pfd.fd = ibuf->fd; + pfd.revents = POLLIN; + + r = poll(&pfd, 1, 0); + if (r == -1) + fatal("poll"); + return r; +} + +void +player_enqueue(struct imsg *imsg) +{ + size_t datalen; + + if (nextfd != -1) + fatalx("track already enqueued"); + + datalen = IMSG_DATA_SIZE(*imsg); + if (datalen != sizeof(nextpath)) + fatalx("%s: size mismatch", __func__); + memcpy(nextpath, imsg->data, sizeof(nextpath)); + if (nextpath[datalen-1] != '\0') + fatalx("%s: corrupted path", __func__); + if ((nextfd = imsg->fd) == -1) + fatalx("%s: got invalid file descriptor", __func__); + log_debug("enqueued %s", nextpath); +} + +/* process only one message */ +int +player_dispatch(void) +{ + struct imsg imsg; + ssize_t n; + int ret; + + if (halted != 0) + return IMSG_STOP; + + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatalx("imsg_read"); + if (n == 0) + fatalx("pipe closed"); + + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* no more messages */ + fatalx("expected at least a message"); + + ret = imsg.hdr.type; + switch (imsg.hdr.type) { + case IMSG_PLAY: + player_enqueue(&imsg); + ret = IMSG_STOP; + break; + case IMSG_RESUME: + case IMSG_PAUSE: + case IMSG_STOP: + break; + default: + fatalx("unknown imsg %d", imsg.hdr.type); + } + + return ret; +} + +void +player_senderr(void) +{ + imsg_compose(ibuf, IMSG_ERR, 0, 0, -1, NULL, 0); + imsg_flush(ibuf); +} + +void +player_sendeof(void) +{ + imsg_compose(ibuf, IMSG_EOF, 0, 0, -1, NULL, 0); + imsg_flush(ibuf); +} + +void +player_playnext(void) +{ + int fd = nextfd; + + assert(nextfd != -1); + nextfd = -1; + + /* XXX: use magic(5) for this, not file extensions */ + if (strstr(nextpath, ".ogg") != NULL) + play_oggvorbis(fd); + else if (strstr(nextpath, ".mp3") != NULL) + play_mp3(fd); + else if (strstr(nextpath, ".flac") != NULL) + play_flac(fd); + else if (strstr(nextpath, ".opus") != NULL) + play_opus(fd); + else { + log_warnx("unknown file type for %s", nextpath); + player_senderr(); + return; + } + + player_sendeof(); +} + +int +player_pause(void) +{ + int r; + + r = player_dispatch(); + return r == IMSG_RESUME; +} + +int +player_shouldstop(void) +{ + if (!player_pendingimsg()) + return 0; + + switch (player_dispatch()) { + case IMSG_PAUSE: + if (player_pause()) + break; + /* fallthrough */ + case IMSG_STOP: + return 1; + } + + return 0; +} + +int +player(int debug, int verbose) +{ + int flags; + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + setproctitle("player"); + log_procinit("player"); + +#if 0 + { + static int attached; + + while (!attached) + sleep(1); + } +#endif + + + audio_init(); + + /* mark fd as blocking i/o mode */ + if ((flags = fcntl(3, F_GETFL)) == -1) + fatal("fcntl(F_GETFL)"); + if (fcntl(3, F_SETFL, flags & ~O_NONBLOCK) == -1) + fatal("fcntl F_SETFL O_NONBLOCK"); + + ibuf = xmalloc(sizeof(*ibuf)); + imsg_init(ibuf, 3); + + signal(SIGINT, player_signal_handler); + signal(SIGTERM, player_signal_handler); + + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + if (pledge("stdio recvfd", NULL) == -1) + fatal("pledge"); + + while (!halted) { + while (nextfd == -1) + assert(player_dispatch() == IMSG_STOP); + + player_playnext(); + } + + return 0; +} blob - /dev/null blob + ce442b965abad7a55c1e19642875989337955088 (mode 644) --- /dev/null +++ player_flac.c @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "amused.h" + +static FLAC__StreamDecoderWriteStatus +writecb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, + const int32_t * const *buffer, void *data) +{ + static uint8_t buf[BUFSIZ]; + int i; + size_t len; + + if (player_shouldstop()) + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + for (i = 0, len = 0; i < frame->header.blocksize; ++i) { + if (len+4 >= sizeof(buf)) { + sio_write(hdl, buf, len); + len = 0; + } + + buf[len++] = buffer[0][i] & 0xff; + buf[len++] = (buffer[0][i] >> 8) & 0xff; + + buf[len++] = buffer[1][i] & 0xff; + buf[len++] = (buffer[1][i] >> 8) & 0xff; + } + + if (len != 0) + sio_write(hdl, buf, len); + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static void +metacb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *meta, + void *d) +{ + uint32_t sample_rate; + struct sio_par par; + + if (meta->type == FLAC__METADATA_TYPE_STREAMINFO) { + sample_rate = meta->data.stream_info.sample_rate; + + printf("sample rate: %d\n", sample_rate); + printf("channels: %d\n", meta->data.stream_info.channels); + printf("bps: %d\n", meta->data.stream_info.bits_per_sample); + printf("total samples: %"PRIu64"\n", meta->data.stream_info.total_samples); + + if (player_setrate(sample_rate) == -1) + err(1, "player_setrate"); + + sio_stop(hdl); + + sio_initpar(&par); + par.rate = sample_rate; + if (!sio_setpar(hdl, &par)) + err(1, "sio_setpar"); + if (!sio_getpar(hdl, &par)) + err(1, "sio_getpar"); + /* TODO: check that there is a sane sample rate? */ + if (!sio_start(hdl)) + err(1, "sio_start"); + } +} + +static void +errcb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, + void *data) +{ + warnx("error: %s", FLAC__StreamDecoderErrorStatusString[status]); +} + +void +play_flac(int fd) +{ + FILE *f; + int ok = 1; + FLAC__StreamDecoder *decoder = NULL; + FLAC__StreamDecoderInitStatus init_status; + + if ((f = fdopen(fd, "r")) == NULL) + err(1, "fdopen"); + + decoder = FLAC__stream_decoder_new(); + if (decoder == NULL) + err(1, "flac stream decoder"); + + FLAC__stream_decoder_set_md5_checking(decoder, 1); + + init_status = FLAC__stream_decoder_init_FILE(decoder, f, writecb, + metacb, errcb, NULL); + if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) + errx(1, "flac decoder: %s", + FLAC__StreamDecoderInitStatusString[init_status]); + + ok = FLAC__stream_decoder_process_until_end_of_stream(decoder); + warnx("decoding %s", ok ? "succeeded" : "failed"); + warnx("state: %s", + FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)]); + + FLAC__stream_decoder_delete(decoder); +} blob - /dev/null blob + 5dda0c58eb3755eae21ae708c60579876220804e (mode 644) --- /dev/null +++ player_mad.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "amused.h" + +struct mad_stream mad_stream; +struct mad_frame mad_frame; +struct mad_synth mad_synth; + +struct buffer { + const void *start; + size_t length; + int sample_rate; +}; + +static enum mad_flow +input(void *d, struct mad_stream *stream) +{ + struct buffer *buffer = d; + + if (buffer->length == 0) + return MAD_FLOW_STOP; + + printf("decode time! start=%p, len=%zu\n", buffer->start, buffer->length); + mad_stream_buffer(stream, buffer->start, buffer->length); + buffer->length = 0; + buffer->sample_rate = 0; + return MAD_FLOW_CONTINUE; +} + +/* scale a mad sample to 16 bits */ +static inline int +scale(mad_fixed_t sample) +{ + /* round */ + sample += (1L << (MAD_F_FRACBITS - 16)); + + /* clip */ + if (sample >= MAD_F_ONE) + sample = MAD_F_ONE - 1; + else if (sample < -MAD_F_ONE) + sample -= MAD_F_ONE; + + /* quantize */ + return sample >> (MAD_F_FRACBITS + 1 - 16); +} + +static enum mad_flow +output(void *data, const struct mad_header *header, struct mad_pcm *pcm) +{ + static uint8_t buf[BUFSIZ]; + size_t len; + struct buffer *buffer = data; + int nsamples, i; + uint16_t sample; + const mad_fixed_t *leftch, *rightch; + + if (player_shouldstop()) + return MAD_FLOW_STOP; + + nsamples = pcm->length; + leftch = pcm->samples[0]; + rightch = pcm->samples[1]; + + if (buffer->sample_rate != pcm->samplerate) { + buffer->sample_rate = pcm->samplerate; + if (player_setrate(pcm->samplerate) == -1) + err(1, "player_setrate"); + } + + if (pcm->channels != 2) { + printf("mono not supported!\n"); + return MAD_FLOW_STOP; + } + + for (i = 0, len = 0; i < nsamples; ++i) { + if (len+4 >= sizeof(buf)) { + sio_write(hdl, buf, len); + /* fwrite(buf, 1, len, stdout); */ + len = 0; + } + + sample = scale(*leftch++); + buf[len++] = sample & 0xff; + buf[len++] = (sample >> 8) & 0xff; + + sample = scale(*rightch++); + buf[len++] = sample & 0xff; + buf[len++] = (sample >> 8) & 0xff; + } + + if (len != 0) + /* fwrite(buf, 1, len, stdout); */ + sio_write(hdl, buf, len); + + return MAD_FLOW_CONTINUE; +} + +static enum mad_flow +error(void *d, struct mad_stream *stream, struct mad_frame *frame) +{ + struct buffer *buffer = d; + + warnx("decoding error 0x%04x (%s) at byte offset %zu", + stream->error, mad_stream_errorstr(stream), + stream->this_frame - (const unsigned char *)buffer->start); + + return MAD_FLOW_CONTINUE; +} + +static int +decode(void *m, size_t len) +{ + struct buffer buffer; + struct mad_decoder decoder; + int result; + + /* initialize our private message structure; */ + buffer.start = m; + buffer.length = len; + + /* configure input, output and error functions */ + mad_decoder_init(&decoder, &buffer, input, 0 /* header */, + 0 /* filter */, output, error, 0 /* message */); + + /* start decoding */ + result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC); + + /* release the decoder */ + mad_decoder_finish(&decoder); + + return result; +} + +void +play_mp3(int fd) +{ + struct stat stat; + void *m; + + if (fstat(fd, &stat) == -1) + err(1, "fstat"); + warnx("file size %lld", stat.st_size); + + m = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (m == MAP_FAILED) + err(1, "mmap"); + + decode(m, stat.st_size); + munmap(m, stat.st_size); +} blob - /dev/null blob + 2726b9419c7466e3933d2076a20fa4bc9af8cce9 (mode 644) --- /dev/null +++ player_oggvorbis.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "amused.h" + +#ifndef nitems +#define nitems(x) (sizeof(x)/sizeof(x[0])) +#endif + +void +play_oggvorbis(int fd) +{ + static uint8_t pcmout[4096]; + FILE *f; + OggVorbis_File vf; + int current_section, eof = 0; + + if ((f = fdopen(fd, "r")) == NULL) + err(1, "fdopen"); + + if (ov_open_callbacks(f, &vf, NULL, 0, OV_CALLBACKS_NOCLOSE) < 0) + errx(1, "input is not an Ogg bitstream"); + + { + char **ptr; + vorbis_info *vi; + + vi = ov_info(&vf, -1); + for (ptr = ov_comment(&vf, -1)->user_comments; *ptr; ++ptr) + printf("%s\n", *ptr); + + printf("bitstream is %d channel, %ldHz\n", vi->channels, vi->rate); + + if (player_setrate(vi->rate) == -1) + err(1, "player_setrate"); + } + + while (!eof) { + long ret; + + if (player_shouldstop()) + break; + + ret = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, + ¤t_section); + if (ret == 0) + eof = 1; + else if (ret < 0) + warnx("non-fatal error in the stream %ld", ret); + else { + /* TODO: deal with sample rate changes */ + sio_write(hdl, pcmout, ret); + } + } + + ov_clear(&vf); +} blob - /dev/null blob + 43812e46cda8499ed6cdd447707100c0396ddc55 (mode 644) --- /dev/null +++ player_opus.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "amused.h" + +#ifndef nitems +#define nitems(x) (sizeof(x)/sizeof(x[0])) +#endif + +void +play_opus(int fd) +{ + static uint16_t pcm[BUFSIZ]; + static uint8_t out[BUFSIZ * 2]; + OggOpusFile *of; + void *f; + int ret; + OpusFileCallbacks cb = {NULL, NULL, NULL, NULL}; + int i, li, prev_li = -1; + + if ((f = op_fdopen(&cb, fd, "r")) == NULL) + err(1, "fdopen"); + + of = op_open_callbacks(f, &cb, NULL, 0, &ret); + if (of == NULL) + errx(1, "failed open opus file"); + + for (;;) { + if (player_shouldstop()) + break; + + ret = op_read_stereo(of, pcm, nitems(pcm)); + if (ret == OP_HOLE) /* corrupt file segment? */ + continue; + if (ret < 0) + errx(1, "error decoding file (%d)", ret); + if (ret == 0) + break; /* eof */ + + li = op_current_link(of); + if (li != prev_li) { + const OpusHead *head; + + prev_li = li; + + head = op_head(of, li); + if (head->input_sample_rate) { + if (player_setrate(head->input_sample_rate) + == -1) + err(1, "player_setrate"); + } + } + + for (i = 0; i < 2*ret; ++i) { + out[2*i+0] = pcm[i] & 0xFF; + out[2*i+1] = (pcm[i] >> 8) & 0xFF; + } + sio_write(hdl, out, 4*ret); + } + + op_free(of); +} blob - /dev/null blob + 46a6ac4889b64d4b782b42e6fe09adf838705cd4 (mode 644) --- /dev/null +++ playlist.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "log.h" +#include "xmalloc.h" +#include "playlist.h" + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +struct playlist playlist; +enum play_state play_state; +int repeat_one; +int repeat_all = 1; +ssize_t play_off = -1; + +void +playlist_enqueue(const char *path) +{ + size_t newcap; + + if (playlist.len == playlist.cap) { + newcap = MAX(16, playlist.cap * 1.5); + playlist.songs = xrecallocarray(playlist.songs, playlist.cap, + newcap, sizeof(*playlist.songs)); + playlist.cap = newcap; + } + + playlist.songs[playlist.len++] = xstrdup(path); +} + +const char * +playlist_current(void) +{ + if (playlist.len == 0 || play_off == -1) + return NULL; + + return playlist.songs[play_off]; +} + +const char * +playlist_advance(void) +{ + if (playlist.len == 0 || play_off+1 == playlist.len) + return NULL; + + play_off++; + if (play_off == playlist.len) { + if (repeat_all) + play_off = 0; + else { + play_state = STATE_STOPPED; + play_off = -1; + return NULL; + } + } + + play_state = STATE_PLAYING; + return playlist.songs[play_off]; +} + +void +playlist_reset(void) +{ + play_off = -1; +} + +void +playlist_truncate(void) +{ + size_t i; + + for (i = 0; i < playlist.len; ++i) + free(playlist.songs[i]); + free(playlist.songs); + playlist.songs = NULL; + + playlist.len = 0; + playlist.cap = 0; + play_off = -1; +} blob - /dev/null blob + 3b2e2ed26d78dceb8e3fa7c637cb95218fedc322 (mode 644) --- /dev/null +++ playlist.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef PLAYLIST_H +#define PLAYLIST_H + +struct playlist { + size_t len; + size_t cap; + char **songs; +}; + +enum play_state { + STATE_STOPPED, + STATE_PLAYING, + STATE_PAUSED, +}; + +extern struct playlist playlist; + +extern enum play_state play_state; +extern int repeat_one; +extern int repeat_all; +extern ssize_t play_off; + +void playlist_enqueue(const char *); +const char *playlist_current(void); +const char *playlist_advance(void); +void playlist_reset(void); +void playlist_truncate(void); + +#endif blob - /dev/null blob + f05ceadf51129ffe6559594eeea12fa517db5f69 (mode 644) --- /dev/null +++ xmalloc.c @@ -0,0 +1,100 @@ +/* $OpenBSD: xmalloc.c,v 1.4 2019/06/28 05:44:09 deraadt Exp $ */ +/* + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * Versions of malloc and friends that check their results, and never return + * failure (they call fatal if they encounter an error). + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "xmalloc.h" + +void * +xmalloc(size_t size) +{ + void *ptr; + + if (size == 0) + fatal("xmalloc: zero size"); + ptr = malloc(size); + if (ptr == NULL) + fatal("xmalloc: allocating %zu bytes", size); + return ptr; +} + +void * +xcalloc(size_t nmemb, size_t size) +{ + void *ptr; + + if (size == 0 || nmemb == 0) + fatal("xcalloc: zero size"); + ptr = calloc(nmemb, size); + if (ptr == NULL) + fatal("xcalloc: allocating %zu * %zu bytes", nmemb, size); + return ptr; +} + +void * +xreallocarray(void *ptr, size_t nmemb, size_t size) +{ + void *new_ptr; + + new_ptr = reallocarray(ptr, nmemb, size); + if (new_ptr == NULL) + fatal("xreallocarray: allocating %zu * %zu bytes", + nmemb, size); + return new_ptr; +} + +void * +xrecallocarray(void *ptr, size_t oldnmemb, size_t nmemb, size_t size) +{ + void *new_ptr; + + new_ptr = recallocarray(ptr, oldnmemb, nmemb, size); + if (new_ptr == NULL) + fatal("xrecallocarray: allocating %zu * %zu bytes", + nmemb, size); + return new_ptr; +} + +char * +xstrdup(const char *str) +{ + char *cp; + + if ((cp = strdup(str)) == NULL) + fatal("xstrdup"); + return cp; +} + +int +xasprintf(char **ret, const char *fmt, ...) +{ + va_list ap; + int i; + + va_start(ap, fmt); + i = vasprintf(ret, fmt, ap); + va_end(ap); + + if (i == -1) + fatal("xasprintf"); + + return i; +} blob - /dev/null blob + 5e11cb4e79d681af3c5b8787d9308302336a092f (mode 644) --- /dev/null +++ xmalloc.h @@ -0,0 +1,31 @@ +/* $OpenBSD: xmalloc.h,v 1.3 2015/11/17 18:25:03 tobias Exp $ */ + +/* + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * Created: Mon Mar 20 22:09:17 1995 ylo + * + * Versions of malloc and friends that check their results, and never return + * failure (they call fatal if they encounter an error). + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +#ifndef XMALLOC_H +#define XMALLOC_H + +void *xmalloc(size_t); +void *xcalloc(size_t, size_t); +void *xreallocarray(void *, size_t, size_t); +void *xrecallocarray(void *, size_t, size_t, size_t); +char *xstrdup(const char *); +int xasprintf(char **, const char *, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); + +#endif /* XMALLOC_H */