├── .gitignore ├── CHANGES ├── LICENSE ├── Makefile ├── README.md ├── amused.1 ├── amused.c ├── amused.h ├── audio.h ├── audio_alsa.c ├── audio_ao.c ├── audio_oboe.cpp ├── audio_oss.c ├── audio_sndio.c ├── compat ├── Makefile ├── endian.h ├── explicit_bzero.c ├── fcntl.h ├── flock.c ├── freezero.c ├── getopt.c ├── getprogname.c ├── imsg-buffer.c ├── imsg.c ├── imsg │ └── imsg.h ├── memmem.c ├── memrchr.c ├── queue │ └── sys │ │ └── queue.h ├── reallocarray.c ├── recallocarray.c ├── setproctitle.c ├── sha1.c ├── sha1 │ └── sha1.h ├── stdio.h ├── stdlib.h ├── string.h ├── strlcat.c ├── strlcpy.c ├── strtonum.c ├── sys │ └── time.h ├── timespecsub.c └── unistd.h ├── configure ├── configure.local.example ├── contrib ├── README.md ├── amused-monitor ├── amused-termux-notification └── amusing ├── control.c ├── control.h ├── ctl.c ├── ev.c ├── ev.h ├── log.c ├── log.h ├── mpris2 ├── Makefile ├── amused-mpris2.1 ├── mpris2.c └── spec.h ├── player.c ├── player.h ├── player_123.c ├── player_flac.c ├── player_oggvorbis.c ├── player_opus.c ├── playlist.c ├── playlist.h ├── songmeta ├── Makefile ├── flac.c ├── id3v1.c ├── id3v2.c ├── ogg.c ├── ogg.h ├── opus.c ├── songmeta.1 ├── songmeta.c ├── songmeta.h ├── text.c └── vorbis.c ├── tests.c ├── web ├── Makefile ├── amused-web.1 ├── bufio.c ├── bufio.h ├── http.c ├── http.h ├── web.c ├── ws.c └── ws.h ├── xmalloc.c └── xmalloc.h /.gitignore: -------------------------------------------------------------------------------- 1 | amused 2 | config.h 3 | config.h.old 4 | config.log 5 | config.log.old 6 | config.mk 7 | mpris2/amused-mpris2 8 | songmeta/songmeta 9 | web/amused-web 10 | **/*.d 11 | **/*.o 12 | **/obj 13 | **/tags 14 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | * amused 0.19; 2025-05-17 2 | - add compat for reallocarray; should fix build on macOS 3 | - sync all the decoders internal buffer size, and bump it 4 | - use substrings instead of regexp for `amused jump' 5 | 6 | * amused 0.18; 2025-02-23 7 | - convert to new imsg APIs and updated bundled imsg 8 | - small tweak to amused.1 9 | - amused-web: add a dark theme 10 | 11 | * amused 0.17; 2025-02-15 12 | - sync'ed subcommand usages with the manpage 13 | - renamed the `prev' subcommand to `previous' for consistency 14 | - renamed the `prev' event to `previous' as well 15 | - improvements to the manpage 16 | - add `shuffle' subcommand 17 | 18 | * amused 0.16; 2024-09-01 19 | - unbreak the build on various OSes 20 | - add an oss audio backend 21 | - plug a memory leak in amused-web 22 | - add MPRIS2 support via a separate executable amused-mpris2 23 | - amused-web now daemonizes by default; added -d to stay in the foreground 24 | - fixed a bug in ao and oss backends that made amused advance at the wrong rate 25 | 26 | * amused 0.15; 2024-05-26 27 | - fixed build of amused-web on linux 28 | - add songmeta: a song metadata extractor; not build by default 29 | - reworked the portable layer; libmd now it's optional on linux and macos 30 | - amused-web now it's built by default; ./configure --without-web to disable 31 | - added ./configure --with-Werror to build with -Werror 32 | 33 | * amused 0.14; 2024-01-21 34 | - amused-web: fix typo in error message 35 | - add a libao audio backend 36 | - add an audio backend for android (using oboe) 37 | - add contrib/amused-termux-notification (for android) 38 | - honour $TMPDIR if set 39 | - update bundled imsg 40 | - switched to the new imsg APIs 41 | 42 | * amused 0.13; 2023-09-09 43 | - few configure enhancements: allow CC, CFLAGS and LDADD_LIB_* as args too 44 | - fix the compat test for getdtablecount(3) 45 | - drop the dependency on libevent 46 | - add amused-web to control amused remotely (optional; needs libmd on linux) 47 | - contrib/amusing: use fancy unicode characters for the buttons 48 | - unbreak percentage relative seeking 49 | - contrib/amused-monitor: fix handling of non-ASCII characters (finally!) 50 | - update bundled imsg 51 | - add missing HAVE_IMSG in config.h (from tb@; thanks!) 52 | 53 | * amused 0.12; 2023-05-17 54 | - few ALSA backend bug fixes 55 | - set up the onmove callback on ALSA too 56 | - fix build with BACKEND=alsa; from ben, thanks! 57 | 58 | * amused 0.11; 2023-05-02 59 | - added support for ALSA 60 | - retry when sio_revents fails with EAGAIN 61 | - log errno too when sio_revents fails 62 | - added contrib/amusing: a GUI written in Tcl/Tk 63 | - amused-monitor: use new extended events info 64 | - enriched `mode' and `seek' events reporting in `amused monitor' 65 | - various minor improvements to amused.1 66 | - amused-monitor: fix error for tracks hours long 67 | - amused-monitor: add pledge 68 | - amused-monitor: fix rewind 69 | - amused-monitor: fix off by one 70 | - fix sndio detection 71 | 72 | * amused 0.10; 2022-07-13 73 | - changed the default output of 'amused status' 74 | - print the status after 'amused consume' or 'amused repeat' 75 | - fixed 'amused load' with an empty playlist 76 | - the on|off argument to 'amused consume/repeat' is now optional 77 | - added a consume mode 78 | - pledge earlier 79 | - rework (and document) the events reported by "amused monitor" (some were added/removed) 80 | - added contrib/amused-monitor: ncurses tui written in perl (not installed by default) 81 | - added 'amused status -f ' to customize the output format 82 | - clarify how non-regular or non-supported files in the playlist are treated 83 | - handle fdopen failures gracefully 84 | - use (a modified version of) kristaps' oconfigure; amused is portable now! 85 | - added ability to seek 86 | - keep track of current position and total duration of the track 87 | - don't get stuck if the server fails to start 88 | - improved error reporting when failing to play a song 89 | 90 | * amused 0.9; 2022-06-12 91 | - reset the path buffer; fixes the "malformed data" error during `load' 92 | 93 | * amused 0.8; 2022-06-12 94 | - try to recover from mp3 decoding errors 95 | - plug a memory leak in player_dispatch 96 | - don't use realpath(3) in `amused add' 97 | - use a stricter pledge for `amused add' 98 | - use canonpath (from kern_pledge.c) for paths handling 99 | - non-blocking usage of sndio: avoids artificial gaps between tracks 100 | - use sio_flush to stop the playback 101 | - automatically skip all non-regular files, not only directories 102 | 103 | * amused 0.7; 2022-05-19 104 | - amused monitor optionally accepts a list of events to report 105 | - consume all enqueued messages before calling imsg_read 106 | 107 | * amused 0.6; 2022-05-10 108 | - manpage tweaks 109 | - fix file descriptor leak on player_playnext failure 110 | - added a simple filetype detector instead of relying on file extensions 111 | 112 | * amused 0.5; 2022-03-26 113 | - unbreak opus and ogg vorbis 114 | - handle flacs with bps != 16 and/or channels != 2 115 | - speed up `load' a lot (especially over NFS) 116 | 117 | * amused 0.4; 2022-03-14 118 | - second part of the "unbreak flac" due to carelessly refactoring 119 | 120 | * amused 0.3; 2022-03-13 121 | - unbreak flac: add back missing counter reset 122 | 123 | * amused 0.2; 2022-03-10 124 | - keep the current song if `load' input was generated by show -p 125 | - update the current song after `load' if the player was stopped 126 | - follow tmux behavior wrt how the daemon is automatically started 127 | - changed the output of `amused status' to simplify parsing 128 | - correctly handle arguments for subcommands 129 | - correctly pledge the player process with "stdio recvfd audio" 130 | - manpage improvements 131 | 132 | * amused 0.1; 2022-02-24 133 | - first public version (and probably last too, the thing is finished) 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Omar Polo 2 | 3 | Permission to use, copy, modify, and/or distribute this software for 4 | any purpose with or without fee is hereby granted, provided that the 5 | above copyright notice and this permission notice appear in all 6 | copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 9 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 10 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 11 | AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 12 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 13 | PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all mpris2 songmeta web clean distclean \ 2 | install install-amused install-songmeta install-web 3 | 4 | VERSION = 0.20 5 | PROG = amused 6 | DISTNAME = ${PROG}-${VERSION} 7 | 8 | SOURCES = amused.c \ 9 | control.c \ 10 | ctl.c \ 11 | ev.c \ 12 | log.c \ 13 | player.c \ 14 | player_123.c \ 15 | player_flac.c \ 16 | player_oggvorbis.c \ 17 | player_opus.c \ 18 | playlist.c \ 19 | xmalloc.c 20 | 21 | OBJS = ${SOURCES:.c=.o} audio_${BACKEND}.o ${COBJS:%=compat/%} 22 | 23 | HEADERS = amused.h \ 24 | audio.h \ 25 | control.h \ 26 | ev.h \ 27 | log.h \ 28 | player.h \ 29 | playlist.h \ 30 | xmalloc.h 31 | 32 | DISTFILES = CHANGES \ 33 | LICENSE \ 34 | Makefile \ 35 | README.md \ 36 | amused.1 \ 37 | configure \ 38 | configure.local.example \ 39 | tests.c \ 40 | ${HEADERS} \ 41 | ${SOURCES} \ 42 | audio_alsa.c \ 43 | audio_ao.c \ 44 | audio_oboe.cpp \ 45 | audio_oss.c \ 46 | audio_sndio.c 47 | 48 | TOPDIR = . 49 | 50 | include config.mk 51 | 52 | all: ${PROGS} 53 | 54 | config.mk config.h: configure tests.c 55 | @echo "$@ is out of date; please run ./configure" 56 | @exit 1 57 | 58 | # -- targets -- 59 | 60 | ${PROG}: ${OBJS} 61 | ${CC} -o $@ ${OBJS} ${LDFLAGS} ${LDADD} ${LDADD_LIB_IMSG} \ 62 | ${LDADD_DECODERS} ${LDADD_LIB_SOCKET} ${LDADD_BACKEND} 63 | 64 | mpris2: 65 | ${MAKE} -C mpris2 66 | 67 | songmeta: 68 | ${MAKE} -C songmeta 69 | 70 | web: 71 | ${MAKE} -C web 72 | 73 | clean: 74 | rm -f ${OBJS} ${OBJS:.o=.d} ${PROG} 75 | -${MAKE} -C mpris2 clean 76 | -${MAKE} -C songmeta clean 77 | -${MAKE} -C web clean 78 | 79 | distclean: clean 80 | rm -f config.mk config.h config.h.old config.log config.log.old 81 | 82 | install: 83 | ${MAKE} ${PROGS:%=install-%} 84 | 85 | install-amused: 86 | mkdir -p ${DESTDIR}${BINDIR} 87 | mkdir -p ${DESTDIR}${MANDIR}/man1 88 | ${INSTALL_PROGRAM} ${PROG} ${DESTDIR}${BINDIR} 89 | ${INSTALL_MAN} amused.1 ${DESTDIR}${MANDIR}/man1/${PROG}.1 90 | 91 | install-mpris2: 92 | ${MAKE} -C mpris2 install 93 | 94 | install-songmeta: 95 | ${MAKE} -C songmeta install 96 | 97 | install-web: 98 | ${MAKE} -C web install 99 | 100 | install-local: amused mpris2 songmeta web 101 | mkdir -p ${HOME}/bin 102 | ${INSTALL_PROGRAM} ${PROG} ${HOME}/bin 103 | ${MAKE} -C mpris2 install-local 104 | ${MAKE} -C songmeta install-local 105 | ${MAKE} -C web install-local 106 | 107 | uninstall: 108 | rm ${DESTDIR}${BINDIR}/${PROG} 109 | rm ${DESTDIR}${MANDIR}/man1/${PROG}.1 110 | 111 | .c.o: 112 | ${CC} -I. -Icompat ${CFLAGS} -DBUFIO_WITHOUT_TLS -c $< -o $@ 113 | 114 | # --- maintainer targets --- 115 | 116 | dist: ${DISTNAME}.sha256 117 | 118 | ${DISTNAME}.sha256: ${DISTNAME}.tar.gz 119 | sha256 ${DISTNAME}.tar.gz > $@ 120 | 121 | ${DISTNAME}.tar.gz: ${DISTFILES} 122 | mkdir -p .dist/${DISTNAME} 123 | ${INSTALL} -m 0644 ${DISTFILES} .dist/${DISTNAME} 124 | cd .dist/${DISTNAME} && chmod 755 configure 125 | cd .dist/${DISTNAME} && cp -R ../../contrib . && \ 126 | chmod 755 contrib/amused-monitor 127 | ${MAKE} -C compat DESTDIR=${PWD}/.dist/${DISTNAME}/compat dist 128 | ${MAKE} -C mpris2 DESTDIR=${PWD}/.dist/${DISTNAME}/mpris2 dist 129 | ${MAKE} -C songmeta DESTDIR=${PWD}/.dist/${DISTNAME}/songmeta dist 130 | ${MAKE} -C web DESTDIR=${PWD}/.dist/${DISTNAME}/web dist 131 | cd .dist && tar zcf ../$@ ${DISTNAME} 132 | rm -rf .dist/ 133 | 134 | # --- dependency management --- 135 | 136 | # these .d files are produced during the first build if the compiler 137 | # supports it. 138 | 139 | -include amused.d 140 | -include audio_alsa.d 141 | -include audio_ao.d 142 | -include audio_oboe.d 143 | -include audio_oss.d 144 | -include audio_sndio.d 145 | -include control.d 146 | -include ctl.d 147 | -include ev.d 148 | -include log.d 149 | -include player.d 150 | -include player_123.d 151 | -include player_flac.d 152 | -include player_oggvorbis.d 153 | -include player_opus.d 154 | -include playlist.d 155 | -include xmalloc.d 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amused 2 | 3 | amused is a music player. It doesn't have any amazing features 4 | built-in, on the contrary: it's quite minimal (a fancy word to say 5 | that does very little.) It composes well, or aims to do so, with 6 | other tools though. 7 | 8 | The main feature is that audio decoding runs in a sandboxed process 9 | under `pledge("stdio recvfd audio")` (on OpenBSD at least.) 10 | 11 | It's available on the OpenBSD port tree starting from 7.1 12 | 13 | 14 | ## Building 15 | 16 | The dependencies are: 17 | 18 | - flac 19 | - libmpg123 20 | - libvorbis 21 | - opusfile 22 | - libsndio or libasound (ALSA) or libao 23 | - libmd (optional; needed for amused-web on linux and Mac) 24 | 25 | Then, to build: 26 | 27 | $ ./configure 28 | $ make 29 | # make install # eventually 30 | 31 | To include the metadata extractor utility, songmeta, use: 32 | 33 | $ ./configure --with-songmeta 34 | 35 | To include the DBus control interface for amused for MPRIS2 support, use: 36 | 37 | $ ./configure --with-mpris2 38 | 39 | `amused-mpris2` requires glib2. 40 | 41 | To disable amused-web: 42 | 43 | $ ./configure --without-web 44 | 45 | The build can be customized by passing arguments to the configure 46 | script or by using a `configure.local` file; see `./configure -h` 47 | and [`configure.local.example`](configure.local.example) for more 48 | information. 49 | 50 | For each library the `configure` script first tries to see if they're 51 | available without any extra flags, then tries again with some 52 | hard-coded flags (e.g. `-lFLAC` for flac) and finally resorts to 53 | pkg-config if available. pkg-config auto-detection can be disable by 54 | passing `PKG_CONFIG=false` (or the empty string) 55 | 56 | For Linux users with libbsd installed, the configure script can be 57 | instructed to use libbsd exclusively as follows: 58 | 59 | $ CFLAGS="$(pkg-config --cflags libbsd-overlay)" \ 60 | ./configure LDFLAGS="$(pkg-config --libs libbsd-overlay)" 61 | 62 | To force the use of one specific audio backend and not simply the first 63 | one found, pass `--backend` as: 64 | 65 | $ ./configure --backend=alsa # or sndio, or ao 66 | 67 | 68 | ## Usage 69 | 70 | The fine man page has all nitty gritty details, but the TL;DR is 71 | 72 | - enqueue music with `amused add files...` or `amused load 2 | .\" 3 | .\" Permission to use, copy, modify, and distribute this software for any 4 | .\" purpose with or without fee is hereby granted, provided that the above 5 | .\" copyright notice and this permission notice appear in all copies. 6 | .\" 7 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | .\" 15 | .Dd February 23, 2025 16 | .Dt AMUSED 1 17 | .Os 18 | .Sh NAME 19 | .Nm amused 20 | .Nd music player 21 | .Sh SYNOPSIS 22 | .Nm 23 | .Op Fl dv 24 | .Op Fl s Ar socket 25 | .Oo 26 | .Ar command 27 | .Op Ar argument ... 28 | .Oc 29 | .Sh DESCRIPTION 30 | .Nm 31 | is a simple music player. 32 | It manages a single queue of music files. 33 | At the end of every track, 34 | .Nm 35 | proceeds to the next one in the queue, or with the first one if 36 | it was the last one or if the track wasn't part of the queue. 37 | .Pp 38 | .Nm 39 | automatically skips and removes from the playing queue non-regular files, 40 | or ones with a non-recognized audio format. 41 | .Pp 42 | The following options are available: 43 | .Bl -tag -width Ds 44 | .It Fl d 45 | Do not daemonize: 46 | .Nm 47 | will run in the foreground and log to standard error. 48 | It's ignored if any commands are given on the command line, or if the 49 | server is already running. 50 | .It Fl s Ar socket 51 | Use 52 | .Ar socket 53 | instead of the default 54 | .Pa /tmp/amused-UID 55 | to communicate with the daemon. 56 | .It Fl v 57 | Produce more verbose output. 58 | .It Ar command Op Ar argument ... 59 | Specify the command to run. 60 | If no commands are specified, 61 | .Ic status 62 | is assumed. 63 | Commands may be abbreviated to a unique prefix, for example 64 | .Sq rep 65 | can be given instead of 66 | .Ic repeat . 67 | .El 68 | .Pp 69 | The following commands are available: 70 | .Bl -tag -width Ds 71 | .It Cm add Ar 72 | Enqueue the given files at the end of the playing queue. 73 | .It Cm consume Op Cm on Ns | Ns Cm off 74 | Enable or disable the consume mode. 75 | When consume mode is enabled the tracks are removed from the playing queue 76 | once played completely. 77 | Without arguments toggle the current status. 78 | .It Cm flush 79 | Erase the playlist. 80 | .It Cm jump Ar substring 81 | Play the first song in the playing queue that contains the given 82 | case-insensitive 83 | .Ar substring . 84 | .It Cm load Op Ar file 85 | Load a playlist from 86 | .Ar file 87 | or standard input. 88 | A playlist is a list of paths to music files given one per line 89 | and optionally prefixed by 90 | .Sq > \& 91 | or two spaces. 92 | If the list was generated by 93 | .Nm 94 | .Ic show Fl p , 95 | restore also the position in the playing queue. 96 | Otherwise, if already playing something, try to match the currently 97 | played song in the new queue. 98 | .It Cm monitor Op Ar events 99 | Stop indefinitely and print when an event in the comma-separated list 100 | of 101 | .Ar events 102 | happened, or all if not given. 103 | The available 104 | .Ar events 105 | are: 106 | .Pp 107 | .Bl -tag -compact -width Ds 108 | .It add 109 | Added a song to the playlist. 110 | .It jump 111 | Jumped to a different song in the playlist. 112 | .It load 113 | Loaded a new playlist. 114 | .It mode 115 | Mode changed 116 | .Pq repeat or consume . 117 | .It next 118 | Advanced to next song. 119 | .It pause 120 | Paused. 121 | .It play 122 | Playing or resuming a song. 123 | .It previous 124 | Gone back to previous song. 125 | .It seek 126 | Seeked in the current track. 127 | .It stop 128 | Stopped. 129 | .El 130 | .It Cm next 131 | Play the next song. 132 | .It Cm pause 133 | Pause the playback. 134 | .It Cm play 135 | Start or resume the playback. 136 | .It Cm previous 137 | Play the previous song. 138 | .It Cm repeat one Ns | Ns Cm all Op Cm on Ns | Ns Cm off 139 | Enable or disable the automatic repetition of the current track 140 | .Pq Cm one 141 | or of the whole playing queue 142 | .Pq Cm all . 143 | Without arguments toggle the given repeat mode. 144 | .It Cm restart 145 | Play the current song from the beginning. 146 | It's a shorthand for 147 | .Nm 148 | .Cm seek 149 | 0. 150 | .It Cm seek Oo +- Oc Ns Ar time Ns Op % 151 | Seek by the specified amount of 152 | .Ar time 153 | into the current song. 154 | If the argument is prefixed by either 155 | .Sq + 156 | or 157 | .Sq - 158 | then the seek is relative to the current position, otherwise is 159 | absolute. 160 | With a trailing 161 | .Sq % , 162 | the 163 | .Ar time 164 | is interpreted as a percentage of the length of the track. 165 | .Pp 166 | The 167 | .Ar time 168 | can also be expressed as 169 | .Oo Oo Ar hours : Oc Ns Ar minutes : Oc Ns Ar seconds , 170 | but in this case no trailing 171 | .Sq % 172 | is allowed. 173 | .It Cm show Op Fl p 174 | Show the playlist. 175 | With 176 | .Fl p 177 | it prints a 178 | .Dq pretty 179 | list with the current playing song prefixed by 180 | .Sq > \& . 181 | .It Cm shuffle Op Fl a 182 | Shuffle the playing queue after the currently playing track until the 183 | end. 184 | If 185 | .Fl a 186 | is given, all the tracks in the playing queue are shuffled, with 187 | the currently playing one moved at the top. 188 | .It Cm status Op Fl f Ar format 189 | Print playback status and current song. 190 | The 191 | .Ar format 192 | is a comma-separated list of words that select the information to 193 | print, in order. 194 | It defaults to 195 | .Ev AMUSED_STATUS_FORMAT 196 | or to 197 | .Dq status,time:oneline,mode:oneline 198 | if not defined. 199 | The formats available are: 200 | .Pp 201 | .Bl -tag -compact -width time:percentage 202 | .It path 203 | Path of the current song 204 | .It mode:oneline 205 | Mode status in a single line. 206 | .It mode 207 | Repeat all, one and consume, one per line. 208 | .It status 209 | Playback status by the path to the current song. 210 | .It time:oneline 211 | Position and duration in a single line. 212 | .It time:percentage 213 | Percentage of the current position. 214 | .It time:raw 215 | Current position and duration in seconds. 216 | .It time 217 | Current position and duration in a human-readable format. 218 | .El 219 | .It Cm stop 220 | Stop the playback. 221 | .It Cm toggle 222 | Play/pause the playback. 223 | .El 224 | .Sh ENVIRONMENT 225 | .Bl -tag -width AMUSED_STATUS_FORMAT 226 | .It Ev AMUSED_STATUS_FORMAT 227 | The default format used by 228 | .Nm 229 | .Cm status . 230 | .It Ev TEMPDIR 231 | Path to the directory where the control socket is created. 232 | Defaults to 233 | .Pa /tmp . 234 | .El 235 | .Sh FILES 236 | .Bl -tag -width "/tmp/amused-UID" -compact 237 | .It Pa /tmp/amused-UID 238 | .Ux Ns -domain 239 | socket used for communication with the daemon. 240 | .El 241 | .Sh EXAMPLES 242 | Load every file under the current directory recursively: 243 | .Bd -literal -offset indent 244 | $ find . | amused load 245 | .Ed 246 | .Pp 247 | Enqueue all mp3 files in the current directory: 248 | .Bd -literal -offset indent 249 | $ amused add *.mp3 250 | .Ed 251 | .Pp 252 | Recursively add all opus files: 253 | .Bd -literal -offset indent 254 | $ find . -type f -iname \\*.opus -exec amused add {} + 255 | .Ed 256 | .Pp 257 | Save the state of the player to the file 258 | .Pa amused.dump : 259 | .Bd -literal -offset indent 260 | $ amused show -p > amused.dump 261 | .Ed 262 | .Pp 263 | Load a previous state: 264 | .Bd -literal -offset indent 265 | $ amused load < amused.dump 266 | .Ed 267 | .Pp 268 | Remove duplicates: 269 | .Bd -literal -offset indent 270 | $ amused show | sort | uniq | amused load 271 | .Ed 272 | .Pp 273 | Select a song with 274 | .Xr fzf 1 275 | .Bd -literal -offset indent 276 | $ amused jump "$(amused show | fzf +s)" 277 | .Ed 278 | .Sh SEE ALSO 279 | .Xr amused-mpris2 1 , 280 | .Xr amused-web 1 281 | .Sh AUTHORS 282 | .An -nosplit 283 | The 284 | .Nm 285 | utility was written by 286 | .An Omar Polo Aq Mt op@omarpolo.com . 287 | -------------------------------------------------------------------------------- /amused.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, 2023 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef AMUSED_H 18 | #define AMUSED_H 19 | 20 | extern char *csock; 21 | extern int debug; 22 | extern int verbose; 23 | extern int playing; 24 | extern struct imsgev *iev_player; 25 | 26 | #define IMSG_DATA_SIZE(imsg) ((imsg).hdr.len - IMSG_HEADER_SIZE) 27 | 28 | enum imsg_type { 29 | IMSG_PLAY, /* fd + filename */ 30 | IMSG_RESUME, 31 | IMSG_PAUSE, 32 | IMSG_STOP, 33 | IMSG_POS, 34 | IMSG_LEN, 35 | IMSG_EOF, 36 | IMSG_ERR, /* error string */ 37 | 38 | IMSG_CTL_PLAY, /* with optional filename */ 39 | IMSG_CTL_TOGGLE_PLAY, 40 | IMSG_CTL_PAUSE, 41 | IMSG_CTL_STOP, 42 | IMSG_CTL_FLUSH, 43 | IMSG_CTL_SHOW, 44 | IMSG_CTL_STATUS, 45 | IMSG_CTL_NEXT, 46 | IMSG_CTL_PREV, 47 | IMSG_CTL_JUMP, 48 | IMSG_CTL_MODE, /* struct player_mode */ 49 | IMSG_CTL_SEEK, /* struct player_seek */ 50 | IMSG_CTL_SHUFFLE, /* int all */ 51 | 52 | IMSG_CTL_BEGIN, 53 | IMSG_CTL_ADD, /* path to a file */ 54 | IMSG_CTL_COMMIT, /* offset of the track to jump to */ 55 | 56 | IMSG_CTL_MONITOR, /* struct player_event */ 57 | 58 | IMSG_CTL_ERR, 59 | IMSG__LAST, 60 | }; 61 | 62 | struct imsgev { 63 | struct imsgbuf imsgbuf; 64 | void (*handler)(int, int, void *); 65 | int events; 66 | }; 67 | 68 | enum actions { 69 | NONE, 70 | PLAY, 71 | PAUSE, 72 | TOGGLE, 73 | STOP, 74 | RESTART, /* seek to zero */ 75 | ADD, 76 | FLUSH, 77 | SHOW, 78 | STATUS, 79 | PREV, 80 | NEXT, 81 | LOAD, 82 | JUMP, 83 | MODE, 84 | MONITOR, 85 | SEEK, 86 | SHUFFLE, 87 | }; 88 | 89 | struct player_seek { 90 | int64_t offset; 91 | int relative; 92 | int percent; 93 | }; 94 | 95 | struct ctl_command; 96 | 97 | #define MODE_ON +1 98 | #define MODE_OFF 0 99 | #define MODE_UNDEF -1 100 | #define MODE_TOGGLE -2 101 | struct player_mode { 102 | int repeat_one; 103 | int repeat_all; 104 | int consume; 105 | }; 106 | 107 | struct player_status { 108 | char path[PATH_MAX]; 109 | int status; 110 | int64_t position; 111 | int64_t duration; 112 | struct player_mode mode; 113 | }; 114 | 115 | struct player_event { 116 | int event; 117 | int64_t position; 118 | int64_t duration; 119 | struct player_mode mode; 120 | }; 121 | 122 | struct parse_result { 123 | enum actions action; 124 | char **files; 125 | FILE *fp; 126 | int all; 127 | int pretty; 128 | int monitor[IMSG__LAST]; 129 | struct player_mode mode; 130 | struct player_seek seek; 131 | const char *status_format; 132 | struct ctl_command *ctl; 133 | }; 134 | 135 | struct ctl_command { 136 | const char *name; 137 | enum actions action; 138 | int (*main)(struct parse_result *, int, char **); 139 | const char *usage; 140 | }; 141 | 142 | struct playlist; 143 | struct pollfd; 144 | 145 | /* amused.c */ 146 | void spawn_daemon(void); 147 | void imsg_event_add(struct imsgev *iev); 148 | int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, 149 | pid_t, int, const void *, uint16_t); 150 | int main_send_player(uint16_t, int, const void *, size_t); 151 | int main_play_song(const char *); 152 | void main_playlist_jump(struct imsgev *, struct imsg *); 153 | void main_playlist_resume(void); 154 | void main_playlist_advance(void); 155 | void main_playlist_previous(void); 156 | void main_senderr(struct imsgev *, const char *); 157 | void main_enqueue(int, struct playlist *, struct imsgev *, struct imsg *); 158 | void main_send_playlist(struct imsgev *); 159 | void main_send_status(struct imsgev *); 160 | void main_seek(struct player_seek *); 161 | 162 | /* ctl.c */ 163 | __dead void usage(void); 164 | __dead void ctl(int, char **); 165 | 166 | #endif 167 | -------------------------------------------------------------------------------- /audio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, 2023 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | int audio_open(void (*)(void *, int)); 18 | int audio_setup(unsigned int, unsigned int, unsigned int, 19 | struct pollfd *, int); 20 | int audio_nfds(void); 21 | int audio_pollfd(struct pollfd *, int, int); 22 | int audio_revents(struct pollfd *, int); 23 | size_t audio_write(const void *, size_t); 24 | int audio_flush(void); 25 | int audio_stop(void); 26 | -------------------------------------------------------------------------------- /audio_alsa.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include "audio.h" 22 | #include "log.h" 23 | 24 | static snd_pcm_t *pcm; 25 | static size_t bpf; 26 | static void (*onmove_cb)(void *, int); 27 | 28 | int 29 | audio_open(void (*cb)(void *, int)) 30 | { 31 | const char *device = "default"; 32 | int err; 33 | 34 | err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 35 | SND_PCM_NONBLOCK); 36 | if (err < 0) { 37 | log_warnx("playback open error: %s", snd_strerror(err)); 38 | return -1; 39 | } 40 | 41 | onmove_cb = cb; 42 | return 0; 43 | } 44 | 45 | int 46 | audio_setup(unsigned int bits, unsigned int rate, unsigned int channels, 47 | struct pollfd *pfds, int nfds) 48 | { 49 | int err; 50 | snd_pcm_format_t fmt; 51 | 52 | if (bits == 8) { 53 | fmt = SND_PCM_FORMAT_S8; 54 | bpf = 1; 55 | } else if (bits == 16) { 56 | fmt = SND_PCM_FORMAT_S16; 57 | bpf = 2; 58 | } else if (bits == 24) { 59 | fmt = SND_PCM_FORMAT_S24; 60 | bpf = 4; 61 | } else if (bits == 32) { 62 | fmt = SND_PCM_FORMAT_S32; 63 | bpf = 4; 64 | } else { 65 | log_warnx("can't handle %d bits", bits); 66 | return -1; 67 | } 68 | 69 | bpf *= channels; 70 | 71 | err = snd_pcm_set_params(pcm, fmt, SND_PCM_ACCESS_RW_INTERLEAVED, 72 | channels, rate, 1, 500000 /* 0.5s */); 73 | if (err < 0) { 74 | log_warnx("invalid params: %s", snd_strerror(err)); 75 | return -1; 76 | } 77 | 78 | err = snd_pcm_prepare(pcm); 79 | if (err < 0) { 80 | log_warnx("snd_pcm_prepare failed: %s", snd_strerror(err)); 81 | return -1; 82 | } 83 | 84 | return 0; 85 | } 86 | 87 | int 88 | audio_nfds(void) 89 | { 90 | return snd_pcm_poll_descriptors_count(pcm); 91 | } 92 | 93 | int 94 | audio_pollfd(struct pollfd *pfds, int nfds, int events) 95 | { 96 | return snd_pcm_poll_descriptors(pcm, pfds, nfds); 97 | } 98 | 99 | int 100 | audio_revents(struct pollfd *pfds, int nfds) 101 | { 102 | int err; 103 | unsigned short revents; 104 | 105 | err = snd_pcm_poll_descriptors_revents(pcm, pfds, nfds, &revents); 106 | if (err < 0) { 107 | log_warnx("snd revents failure: %s", snd_strerror(err)); 108 | return 0; 109 | } 110 | 111 | return revents; 112 | } 113 | 114 | size_t 115 | audio_write(const void *buf, size_t len) 116 | { 117 | snd_pcm_sframes_t avail, ret; 118 | 119 | /* 120 | * snd_pcm_writei works in terms of FRAMES, not BYTES! 121 | */ 122 | len /= bpf; 123 | 124 | avail = snd_pcm_avail_update(pcm); 125 | if (avail < 0) { 126 | if (avail == -EPIPE) { 127 | log_debug("alsa xrun occurred"); 128 | snd_pcm_recover(pcm, -EPIPE, 1); 129 | return 0; 130 | } 131 | log_warnx("snd_pcm_avail_update failure: %s", 132 | snd_strerror(avail)); 133 | return 0; 134 | } 135 | 136 | if (len > avail) 137 | len = avail; 138 | 139 | ret = snd_pcm_writei(pcm, buf, len); 140 | if (ret < 0) { 141 | log_warnx("snd_pcm_writei failed: %s", snd_strerror(ret)); 142 | return 0; 143 | } 144 | if (onmove_cb) 145 | onmove_cb(NULL, ret); 146 | return ret * bpf; 147 | } 148 | 149 | int 150 | audio_flush(void) 151 | { 152 | int err; 153 | 154 | err = snd_pcm_drop(pcm); 155 | if (err < 0) { 156 | log_warnx("snd_pcm_drop: %s", snd_strerror(err)); 157 | return -1; 158 | } 159 | 160 | return 0; 161 | } 162 | 163 | int 164 | audio_stop(void) 165 | { 166 | int err; 167 | 168 | err = snd_pcm_drain(pcm); 169 | if (err < 0) { 170 | log_warnx("snd_pcm_drain: %s", snd_strerror(err)); 171 | return -1; 172 | } 173 | 174 | return 0; 175 | } 176 | -------------------------------------------------------------------------------- /audio_ao.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "audio.h" 27 | #include "log.h" 28 | 29 | static void (*onmove_cb)(void *, int); 30 | static int sp[2]; /* main, audio thread */ 31 | static pthread_t at; 32 | 33 | static int bpf; 34 | static ao_sample_format fmt; 35 | static char buf[BUFSIZ]; 36 | static size_t buflen; 37 | 38 | static void * 39 | aworker(void *d) 40 | { 41 | ao_sample_format f; 42 | ao_device *device = NULL; 43 | ssize_t r; 44 | int sock = sp[1]; 45 | char ch; 46 | 47 | memset(&f, 0, sizeof(f)); 48 | 49 | log_info("%s: starting", __func__); 50 | 51 | for (;;) { 52 | ch = 1; 53 | if ((r = write(sock, &ch, 1)) == -1) 54 | fatal("write"); 55 | if (r == 0) 56 | break; 57 | 58 | if ((r = read(sock, &ch, 1)) == -1) 59 | fatal("read"); 60 | if (r == 0) 61 | break; 62 | 63 | if (memcmp(&fmt, &f, sizeof(f)) != 0) { 64 | if (device != NULL) 65 | ao_close(device); 66 | device = ao_open_live(ao_default_driver_id(), 67 | &fmt, NULL); 68 | if (device == NULL) { 69 | switch (errno) { 70 | case AO_ENODRIVER: 71 | log_warnx("ao: no driver found"); 72 | break; 73 | case AO_ENOTLIVE: 74 | log_warnx("ao: not a live device"); 75 | break; 76 | case AO_EBADOPTION: 77 | log_warnx("ao: bad option(s)"); 78 | break; 79 | case AO_EOPENDEVICE: 80 | log_warnx("ao: failed to open device"); 81 | break; 82 | case AO_EFAIL: 83 | default: 84 | log_warnx("ao: failed opening driver"); 85 | break; 86 | } 87 | errno = EINVAL; 88 | break; 89 | } 90 | log_info("%s: device (re)opened", __func__); 91 | memcpy(&f, &fmt, sizeof(f)); 92 | } 93 | 94 | if (ao_play(device, buf, buflen) == 0) { 95 | log_warnx("ao_play failed"); 96 | break; 97 | } 98 | } 99 | 100 | log_info("quitting audio thread"); 101 | close(sock); 102 | return NULL; 103 | } 104 | 105 | int 106 | audio_open(void (*cb)(void *, int)) 107 | { 108 | ao_initialize(); 109 | onmove_cb = cb; 110 | 111 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == -1) { 112 | log_warn("socketpair"); 113 | return (-1); 114 | } 115 | 116 | if (pthread_create(&at, NULL, aworker, NULL) == -1) { 117 | log_warn("pthread_create"); 118 | return (-1); 119 | } 120 | 121 | return 0; 122 | } 123 | 124 | int 125 | audio_setup(unsigned int bits, unsigned int rate, unsigned int channels, 126 | struct pollfd *pfds, int nfds) 127 | { 128 | fmt.bits = bits; 129 | fmt.rate = rate; 130 | fmt.channels = channels; 131 | fmt.byte_format = AO_FMT_NATIVE; 132 | fmt.matrix = NULL; 133 | 134 | if (bits == 8) 135 | bpf = 1; 136 | else if (bits == 16) 137 | bpf = 2; 138 | else if (bits == 24 || bits == 32) 139 | bpf = 4; 140 | else { 141 | log_warnx("can't handle %d bits", bits); 142 | return -1; 143 | } 144 | bpf *= channels; 145 | 146 | return 0; 147 | } 148 | 149 | int 150 | audio_nfds(void) 151 | { 152 | return 1; 153 | } 154 | 155 | int 156 | audio_pollfd(struct pollfd *pfds, int nfds, int events) 157 | { 158 | if (nfds != 1) { 159 | errno = EINVAL; 160 | return -1; 161 | } 162 | 163 | pfds[0].fd = sp[0]; 164 | pfds[0].events = POLLIN; 165 | return 0; 166 | } 167 | 168 | int 169 | audio_revents(struct pollfd *pfds, int nfds) 170 | { 171 | if (nfds != 1) { 172 | log_warnx("%s: called with nfds=%d", __func__, nfds); 173 | return 0; 174 | } 175 | 176 | /* don't need to check; if we're here the audio thread is ready */ 177 | return POLLOUT; 178 | } 179 | 180 | size_t 181 | audio_write(const void *data, size_t len) 182 | { 183 | char ch; 184 | ssize_t r; 185 | 186 | if ((r = read(sp[0], &ch, 1)) == -1) { 187 | log_warn("ao/%s: read failed", __func__); 188 | return 0; 189 | } 190 | if (r == 0) 191 | return 0; 192 | 193 | if (len > sizeof(buf)) 194 | len = sizeof(buf); 195 | 196 | memcpy(buf, data, len); 197 | buflen = len; 198 | 199 | ch = 1; 200 | if ((r = write(sp[0], &ch, 1)) == -1) { 201 | log_warn("ao/%s: write failed", __func__); 202 | return 0; 203 | } 204 | if (r == 0) { 205 | log_warnx("ao/%s: write got EOF", __func__); 206 | return 0; 207 | } 208 | 209 | if (onmove_cb) 210 | onmove_cb(NULL, len / bpf); 211 | 212 | return len; 213 | } 214 | 215 | int 216 | audio_flush(void) 217 | { 218 | return 0; 219 | } 220 | 221 | int 222 | audio_stop(void) 223 | { 224 | return 0; 225 | } 226 | -------------------------------------------------------------------------------- /audio_oboe.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | extern "C" { 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | } 25 | 26 | #include 27 | 28 | extern "C" { 29 | #include "audio.h" 30 | #include "log.h" 31 | } 32 | 33 | #define ext extern "C" 34 | 35 | static void (*onmove_cb)(void *, int); 36 | static int sp[2]; /* main, audio thread */ 37 | static pthread_t at; 38 | 39 | static int bpf; 40 | static unsigned int bits, rate, chan; 41 | static oboe::AudioFormat fmt; 42 | static char buf[BUFSIZ]; 43 | static size_t buflen; 44 | 45 | static std::shared_ptr stream; 46 | 47 | static void * 48 | aworker(void *d) 49 | { 50 | unsigned int last_bits, last_rate, last_chan; 51 | ssize_t r; 52 | int sock = sp[1]; 53 | char ch; 54 | 55 | stream = nullptr; 56 | last_bits = last_rate = last_chan = 0; 57 | 58 | log_info("%s: starting", __func__); 59 | for (;;) { 60 | ch = 1; 61 | if ((r = write(sock, &ch, 1)) == -1) 62 | fatal("write"); 63 | if (r == 0) 64 | break; 65 | 66 | if ((r = read(sock, &ch, 1)) == -1) 67 | fatal("read"); 68 | if (r == 0) 69 | break; 70 | 71 | if (bits != last_bits || 72 | rate != last_rate || 73 | chan != last_chan) { 74 | if (stream) { 75 | stream->close(); 76 | stream = nullptr; 77 | } 78 | 79 | last_bits = bits; 80 | last_rate = rate; 81 | last_chan = chan; 82 | 83 | log_debug("setting bits=%d rate=%d chan=%d bpf=%d", 84 | bits, rate, chan, bpf); 85 | 86 | oboe::AudioStreamBuilder streamBuilder; 87 | streamBuilder.setFormat(fmt); 88 | streamBuilder.setSampleRate(rate); 89 | streamBuilder.setChannelCount(chan); 90 | oboe::Result result = streamBuilder.openStream(stream); 91 | if (result != oboe::Result::OK) 92 | fatalx("Error opening stream %s", 93 | oboe::convertToText(result)); 94 | 95 | stream->requestStart(); 96 | } 97 | 98 | // oboe works in terms of FRAMES not BYTES! 99 | unsigned int len = buflen / bpf; 100 | 101 | // XXX should be the timeout in nanoseconds... 102 | auto ret = stream->write(buf, len, 1000000000); 103 | if (!ret) { 104 | fatalx("write failed: %s", 105 | oboe::convertToText(ret.error())); 106 | } 107 | } 108 | 109 | return nullptr; 110 | } 111 | 112 | ext int 113 | audio_open(void (*cb)(void *, int)) 114 | { 115 | onmove_cb = cb; 116 | 117 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == -1) { 118 | log_warn("socketpair"); 119 | return (-1); 120 | } 121 | 122 | if (pthread_create(&at, NULL, aworker, NULL) == -1) { 123 | log_warn("pthread_create"); 124 | return (-1); 125 | } 126 | 127 | return (0); 128 | } 129 | 130 | ext int 131 | audio_setup(unsigned int p_bits, unsigned int p_rate, unsigned int p_chan, 132 | struct pollfd *pfds, int nfds) 133 | { 134 | bits = p_bits; 135 | rate = p_rate; 136 | chan = p_chan; 137 | 138 | if (bits == 8) { 139 | log_warnx("would require a conversion layer..."); 140 | return (-1); 141 | } else if (bits == 16) { 142 | bpf = 2; 143 | fmt = oboe::AudioFormat::I16; 144 | } else if (bits == 24) { 145 | bpf = 4; 146 | fmt = oboe::AudioFormat::I24; 147 | } else if (bits == 32) { 148 | // XXX not so sure... 149 | bpf = 4; 150 | fmt = oboe::AudioFormat::I24; 151 | } else { 152 | log_warnx("can't handle %d bits", bits); 153 | return (-1); 154 | } 155 | 156 | bpf *= chan; 157 | 158 | return (0); 159 | } 160 | 161 | ext int 162 | audio_nfds(void) 163 | { 164 | return 1; 165 | } 166 | 167 | ext int 168 | audio_pollfd(struct pollfd *pfds, int nfds, int events) 169 | { 170 | if (nfds != 1) { 171 | errno = EINVAL; 172 | return -1; 173 | } 174 | 175 | pfds[0].fd = sp[0]; 176 | pfds[0].events = POLLIN; 177 | return (0); 178 | } 179 | 180 | ext int 181 | audio_revents(struct pollfd *pfds, int nfds) 182 | { 183 | if (nfds != 1) { 184 | log_warnx("%s: called with %d nfds", __func__, nfds); 185 | return 0; 186 | } 187 | 188 | /* don't need to check; if we're here the audio thread is ready */ 189 | return POLLOUT; 190 | } 191 | 192 | ext size_t 193 | audio_write(const void *data, size_t len) 194 | { 195 | char ch; 196 | ssize_t r; 197 | 198 | if ((r = read(sp[0], &ch, 1)) == -1) { 199 | log_warn("oboe/%s: read failed", __func__); 200 | return 0; 201 | } 202 | if (r == 0) 203 | return 0; 204 | 205 | if (len > sizeof(buf)) 206 | len = sizeof(buf); 207 | 208 | memcpy(buf, data, len); 209 | buflen = len; 210 | 211 | ch = 1; 212 | if ((r = write(sp[0], &ch, 1)) == -1) { 213 | log_warn("oboe/%s: write failed", __func__); 214 | return 0; 215 | } 216 | if (r == 0) { 217 | log_warnx("oboe/%s: write got EOF", __func__); 218 | return 0; 219 | } 220 | 221 | if (onmove_cb) 222 | onmove_cb(NULL, len / bpf); 223 | 224 | return len; 225 | } 226 | 227 | ext int 228 | audio_flush(void) 229 | { 230 | return 0; // XXX request flush 231 | } 232 | 233 | ext int 234 | audio_stop(void) 235 | { 236 | return 0; // XXX request stop 237 | } 238 | -------------------------------------------------------------------------------- /audio_oss.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "audio.h" 28 | #include "log.h" 29 | 30 | static void (*onmove_cb)(void *, int); 31 | static int bpf; 32 | static int cur_bits, cur_rate, cur_chans; 33 | static int audiofd = -1; 34 | 35 | int 36 | audio_open(void (*cb)(void *, int)) 37 | { 38 | onmove_cb = cb; 39 | if ((audiofd = open("/dev/dsp", O_WRONLY)) == -1) 40 | return -1; 41 | return 0; 42 | } 43 | 44 | int 45 | audio_setup(unsigned int bits, unsigned int rate, unsigned int channels, 46 | struct pollfd *pfds, int nfds) 47 | { 48 | int fmt, forig; 49 | int corig, rorig; 50 | int fpct; 51 | 52 | fpct = (rate * 5) / 100; 53 | if (audiofd != -1) { 54 | if (bits == cur_bits && channels == cur_chans && 55 | cur_rate - fpct <= rate && rate <= cur_rate + fpct) 56 | return 0; 57 | } 58 | 59 | close(audiofd); 60 | if (audio_open(onmove_cb) == -1) 61 | return -1; 62 | 63 | if (bits == 8) { 64 | fmt = AFMT_S8; 65 | bpf = 1; 66 | } else if (bits == 16) { 67 | fmt = AFMT_S16_NE; 68 | bpf = 2; 69 | } else if (bits == 24) { 70 | fmt = AFMT_S24_NE; 71 | bpf = 4; 72 | } else if (bits == 32) { 73 | fmt = AFMT_S32_NE; 74 | bpf = 4; 75 | } else { 76 | log_warnx("can't handle %d bits", bits); 77 | return -1; 78 | } 79 | bpf *= channels; 80 | 81 | forig = fmt; 82 | if (ioctl(audiofd, SNDCTL_DSP_SETFMT, &fmt) == -1) { 83 | log_warn("couldn't set the format"); 84 | return -1; 85 | } 86 | if (forig != fmt) { 87 | errno = ENODEV; 88 | return -1; 89 | } 90 | 91 | corig = channels; 92 | if (ioctl(audiofd, SNDCTL_DSP_CHANNELS, &channels) == -1) { 93 | log_warn("couldn't set the channels"); 94 | return -1; 95 | } 96 | if (corig != channels) { 97 | errno = ENODEV; 98 | return -1; 99 | } 100 | 101 | rorig = rate; 102 | if (ioctl(audiofd, SNDCTL_DSP_SPEED, &rate) == -1) { 103 | log_warn("couldn't set the rate"); 104 | return -1; 105 | } 106 | if (rorig - fpct > rate || rate > rorig + fpct) { 107 | errno = ENODEV; 108 | return -1; 109 | } 110 | 111 | cur_bits = bits; 112 | cur_rate = rate; 113 | cur_chans = channels; 114 | return 0; 115 | } 116 | 117 | int 118 | audio_nfds(void) 119 | { 120 | return 1; 121 | } 122 | 123 | int 124 | audio_pollfd(struct pollfd *pfds, int nfds, int events) 125 | { 126 | if (nfds != 1) { 127 | errno = EINVAL; 128 | return -1; 129 | } 130 | 131 | pfds[0].fd = audiofd; 132 | pfds[0].events = POLLOUT; 133 | return 0; 134 | } 135 | 136 | int 137 | audio_revents(struct pollfd *pfds, int nfds) 138 | { 139 | if (nfds != 1) { 140 | log_warnx("%s: called with nfds=%d", __func__, nfds); 141 | return 0; 142 | } 143 | 144 | /* don't need to check, if we're here we can write */ 145 | return POLLOUT; 146 | } 147 | 148 | size_t 149 | audio_write(const void *buf, size_t len) 150 | { 151 | ssize_t r; 152 | 153 | r = write(audiofd, buf, len); 154 | if (r == -1) { 155 | log_warn("oss write"); 156 | return 0; 157 | } 158 | 159 | if (onmove_cb) 160 | onmove_cb(NULL, r / bpf); 161 | 162 | return r; 163 | } 164 | 165 | int 166 | audio_flush(void) 167 | { 168 | if (ioctl(audiofd, SNDCTL_DSP_POST) == -1) 169 | return -1; 170 | return (0); 171 | } 172 | 173 | int 174 | audio_stop(void) 175 | { 176 | return 0; 177 | } 178 | -------------------------------------------------------------------------------- /audio_sndio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "audio.h" 24 | #include "log.h" 25 | 26 | static struct sio_hdl *hdl; 27 | static struct sio_par par; 28 | static int stopped = 1; 29 | 30 | int 31 | audio_open(void (*onmove_cb)(void *, int)) 32 | { 33 | if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 1)) == NULL) 34 | return -1; 35 | 36 | sio_onmove(hdl, onmove_cb, NULL); 37 | return 0; 38 | } 39 | 40 | int 41 | audio_setup(unsigned int bits, unsigned int rate, unsigned int channels, 42 | struct pollfd *pfds, int nfds) 43 | { 44 | int fpct; 45 | 46 | fpct = (rate * 5) / 100; 47 | 48 | /* don't stop if the parameters are the same */ 49 | if (bits == par.bits && channels == par.pchan && 50 | par.rate - fpct <= rate && rate <= par.rate + fpct) { 51 | if (stopped) 52 | goto start; 53 | return 0; 54 | } 55 | 56 | again: 57 | if (!stopped) { 58 | sio_stop(hdl); 59 | stopped = 1; 60 | } 61 | 62 | sio_initpar(&par); 63 | par.bits = bits; 64 | par.rate = rate; 65 | par.pchan = channels; 66 | if (!sio_setpar(hdl, &par)) { 67 | if (errno == EAGAIN) { 68 | sio_pollfd(hdl, pfds, POLLOUT); 69 | if (poll(pfds, nfds, INFTIM) == -1) 70 | fatal("poll"); 71 | goto again; 72 | } 73 | log_warnx("invalid params (bits=%u, rate=%u, channels=%u)", 74 | bits, rate, channels); 75 | return -1; 76 | } 77 | if (!sio_getpar(hdl, &par)) { 78 | log_warnx("can't get params"); 79 | return -1; 80 | } 81 | 82 | if (par.bits != bits || par.pchan != channels) { 83 | log_warnx("failed to set params"); 84 | return -1; 85 | } 86 | 87 | /* TODO: check sample rate? */ 88 | 89 | start: 90 | if (!sio_start(hdl)) { 91 | log_warn("sio_start"); 92 | return -1; 93 | } 94 | stopped = 0; 95 | return 0; 96 | } 97 | 98 | int 99 | audio_nfds(void) 100 | { 101 | return sio_nfds(hdl); 102 | } 103 | 104 | int 105 | audio_pollfd(struct pollfd *pfds, int nfds, int events) 106 | { 107 | return sio_pollfd(hdl, pfds, events); 108 | } 109 | 110 | int 111 | audio_revents(struct pollfd *pfds, int nfds) 112 | { 113 | return sio_revents(hdl, pfds); 114 | } 115 | 116 | size_t 117 | audio_write(const void *buf, size_t len) 118 | { 119 | return sio_write(hdl, buf, len); 120 | } 121 | 122 | int 123 | audio_flush(void) 124 | { 125 | stopped = 1; 126 | return sio_flush(hdl); 127 | } 128 | 129 | int 130 | audio_stop(void) 131 | { 132 | stopped = 1; 133 | return sio_stop(hdl); 134 | } 135 | -------------------------------------------------------------------------------- /compat/Makefile: -------------------------------------------------------------------------------- 1 | DISTFILES = endian.h explicit_bzero.c fcntl.h flock.c freezero.c \ 2 | getopt.c getprogname.c imsg-buffer.c imsg.c memmem.c \ 3 | memrchr.c recallocarray.c setproctitle.c sha1.c stdlib.h \ 4 | string.h strlcat.c strlcpy.c strtonum.c timespecsub.c \ 5 | unistd.h \ 6 | Makefile 7 | 8 | all: 9 | false 10 | 11 | dist: 12 | mkdir -p ${DESTDIR}/ 13 | ${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/ 14 | mkdir -p ${DESTDIR}/imsg 15 | ${INSTALL} -m 0644 imsg/imsg.h ${DESTDIR}/imsg 16 | mkdir -p ${DESTDIR}/queue/sys 17 | ${INSTALL} -m 0644 queue/sys/queue.h ${DESTDIR}/queue/sys 18 | mkdir -p ${DESTDIR}/sha1 19 | ${INSTALL} -m 0644 sha1/sha1.h ${DESTDIR}/sha1 20 | mkdir -p ${DESTDIR}/sys 21 | ${INSTALL} -m 0644 sys/time.h ${DESTDIR}/sys 22 | -------------------------------------------------------------------------------- /compat/endian.h: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #if HAVE_ENDIAN_H 4 | # include_next 5 | #elif HAVE_SYS_ENDIAN_H 6 | # include 7 | #else 8 | # include 9 | # include 10 | 11 | # define htobe16(x) OSSwapHostToBigInt16(x) 12 | # define htole16(x) OSSwapHostToLittleInt16(x) 13 | # define be16toh(x) OSSwapBigToHostInt16(x) 14 | # define le16toh(x) OSSwapLittleToHostInt16(x) 15 | 16 | # define htobe32(x) OSSwapHostToBigInt32(x) 17 | # define htole32(x) OSSwapHostToLittleInt32(x) 18 | # define be32toh(x) OSSwapBigToHostInt32(x) 19 | # define le32toh(x) OSSwapLittleToHostInt32(x) 20 | 21 | # define htobe64(x) OSSwapHostToBigInt64(x) 22 | # define htole64(x) OSSwapHostToLittleInt64(x) 23 | # define be64toh(x) OSSwapBigToHostInt64(x) 24 | # define le64toh(x) OSSwapLittleToHostInt64(x) 25 | #endif 26 | -------------------------------------------------------------------------------- /compat/explicit_bzero.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Public domain. 3 | * Written by Ted Unangst 4 | */ 5 | 6 | #include "config.h" 7 | 8 | #include 9 | 10 | /* 11 | * explicit_bzero - don't let the compiler optimize away bzero 12 | */ 13 | 14 | #if HAVE_MEMSET_S 15 | 16 | void 17 | explicit_bzero(void *p, size_t n) 18 | { 19 | if (n == 0) 20 | return; 21 | (void)memset_s(p, n, 0, n); 22 | } 23 | 24 | #else /* HAVE_MEMSET_S */ 25 | 26 | #include 27 | 28 | /* 29 | * Indirect memset through a volatile pointer to hopefully avoid 30 | * dead-store optimisation eliminating the call. 31 | */ 32 | static void (* volatile ssh_memset)(void *, int, size_t) = 33 | (void (*volatile)(void *, int, size_t))memset; 34 | 35 | void 36 | explicit_bzero(void *p, size_t n) 37 | { 38 | if (n == 0) 39 | return; 40 | /* 41 | * clang -fsanitize=memory needs to intercept memset-like functions 42 | * to correctly detect memory initialisation. Make sure one is called 43 | * directly since our indirection trick above successfully confuses it. 44 | */ 45 | #if defined(__has_feature) 46 | # if __has_feature(memory_sanitizer) 47 | memset(p, 0, n); 48 | # endif 49 | #endif 50 | 51 | ssh_memset(p, 0, n); 52 | } 53 | 54 | #endif /* HAVE_MEMSET_S */ 55 | -------------------------------------------------------------------------------- /compat/fcntl.h: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include_next "fcntl.h" 3 | 4 | #if !HAVE_FLOCK 5 | int flock(int, int); 6 | #endif 7 | -------------------------------------------------------------------------------- /compat/flock.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | */ 9 | 10 | #include "config.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | /* 17 | * flock(2) emulation on top of fcntl advisory locks. This is "good 18 | * enough" for amused, not a _real_ emulation. flock and fcntl locks 19 | * have subtly different behaviours! 20 | */ 21 | int 22 | flock(int fd, int op) 23 | { 24 | struct flock l; 25 | int cmd; 26 | 27 | memset(&l, 0, sizeof(l)); 28 | l.l_whence = SEEK_SET; 29 | 30 | if (op & LOCK_SH) 31 | l.l_type = F_RDLCK; 32 | else if (op & LOCK_EX) 33 | l.l_type = F_WRLCK; 34 | else { 35 | errno = EINVAL; 36 | return -1; 37 | } 38 | 39 | cmd = F_SETLKW; 40 | if (op & LOCK_NB) 41 | cmd = F_SETLK; 42 | 43 | return fcntl(fd, cmd, &l); 44 | } 45 | -------------------------------------------------------------------------------- /compat/freezero.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | */ 9 | 10 | #include "config.h" 11 | 12 | #include 13 | #include 14 | 15 | void 16 | freezero(void *ptr, size_t len) 17 | { 18 | if (ptr == NULL) 19 | return; 20 | memset(ptr, 0, len); 21 | free(ptr); 22 | } 23 | -------------------------------------------------------------------------------- /compat/getopt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1987, 1993, 1994 3 | * The Regents of the University of California. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. Neither the name of the University nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software 15 | * without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | */ 29 | 30 | #include "config.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | int BSDopterr = 1, /* if error message should be printed */ 38 | BSDoptind = 1, /* index into parent argv vector */ 39 | BSDoptopt, /* character checked for validity */ 40 | BSDoptreset; /* reset getopt */ 41 | char *BSDoptarg; /* argument associated with option */ 42 | 43 | #define BADCH (int)'?' 44 | #define BADARG (int)':' 45 | #define EMSG "" 46 | 47 | /* 48 | * getopt -- 49 | * Parse argc/argv argument vector. 50 | */ 51 | int 52 | BSDgetopt(int nargc, char *const *nargv, const char *ostr) 53 | { 54 | static const char *place = EMSG; /* option letter processing */ 55 | char *oli; /* option letter list index */ 56 | 57 | if (ostr == NULL) 58 | return (-1); 59 | 60 | if (BSDoptreset || !*place) { /* update scanning pointer */ 61 | BSDoptreset = 0; 62 | if (BSDoptind >= nargc || *(place = nargv[BSDoptind]) != '-') { 63 | place = EMSG; 64 | return (-1); 65 | } 66 | if (place[1] && *++place == '-') { /* found "--" */ 67 | if (place[1]) 68 | return (BADCH); 69 | ++BSDoptind; 70 | place = EMSG; 71 | return (-1); 72 | } 73 | } /* option letter okay? */ 74 | if ((BSDoptopt = (int)*place++) == (int)':' || 75 | !(oli = strchr(ostr, BSDoptopt))) { 76 | /* 77 | * if the user didn't specify '-' as an option, 78 | * assume it means -1. 79 | */ 80 | if (BSDoptopt == (int)'-') 81 | return (-1); 82 | if (!*place) 83 | ++BSDoptind; 84 | if (BSDopterr && *ostr != ':') 85 | (void)fprintf(stderr, 86 | "%s: unknown option -- %c\n", getprogname(), 87 | BSDoptopt); 88 | return (BADCH); 89 | } 90 | if (*++oli != ':') { /* don't need argument */ 91 | BSDoptarg = NULL; 92 | if (!*place) 93 | ++BSDoptind; 94 | } 95 | else { /* need an argument */ 96 | if (*place) /* no white space */ 97 | BSDoptarg = (char *)place; 98 | else if (nargc <= ++BSDoptind) { /* no arg */ 99 | place = EMSG; 100 | if (*ostr == ':') 101 | return (BADARG); 102 | if (BSDopterr) 103 | (void)fprintf(stderr, 104 | "%s: option requires an argument -- %c\n", 105 | getprogname(), BSDoptopt); 106 | return (BADCH); 107 | } 108 | else /* white space */ 109 | BSDoptarg = nargv[BSDoptind]; 110 | place = EMSG; 111 | ++BSDoptind; 112 | } 113 | return (BSDoptopt); /* dump back option letter */ 114 | } 115 | -------------------------------------------------------------------------------- /compat/getprogname.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Nicholas Marriott 3 | * Copyright (c) 2017 Kristaps Dzonsons 4 | * Copyright (c) 2020 Stephen Gregoratto 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include "config.h" 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #if HAVE_GETEXECNAME 27 | const char * 28 | getprogname(void) 29 | { 30 | return getexecname(); 31 | } 32 | #elif HAVE_PROGRAM_INVOCATION_SHORT_NAME 33 | const char * 34 | getprogname(void) 35 | { 36 | return (program_invocation_short_name); 37 | } 38 | #elif HAVE___PROGNAME 39 | const char * 40 | getprogname(void) 41 | { 42 | extern char *__progname; 43 | 44 | return (__progname); 45 | } 46 | #else 47 | #warning No getprogname available. 48 | const char * 49 | getprogname(void) 50 | { 51 | return ("amused"); 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /compat/imsg/imsg.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: imsg.h,v 1.19 2024/11/26 13:57:31 claudio Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2023 Claudio Jeker 5 | * Copyright (c) 2006, 2007 Pierre-Yves Ritschard 6 | * Copyright (c) 2006, 2007, 2008 Reyk Floeter 7 | * Copyright (c) 2003, 2004 Henning Brauer 8 | * 9 | * Permission to use, copy, modify, and distribute this software for any 10 | * purpose with or without fee is hereby granted, provided that the above 11 | * copyright notice and this permission notice appear in all copies. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 14 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 16 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 18 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 19 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | */ 21 | 22 | #ifndef _IMSG_H_ 23 | #define _IMSG_H_ 24 | 25 | #include 26 | #include 27 | 28 | #define IBUF_READ_SIZE 65535 29 | #define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) 30 | #define MAX_IMSGSIZE 16384 31 | 32 | struct ibuf { 33 | TAILQ_ENTRY(ibuf) entry; 34 | unsigned char *buf; 35 | size_t size; 36 | size_t max; 37 | size_t wpos; 38 | size_t rpos; 39 | int fd; 40 | }; 41 | 42 | struct msgbuf; 43 | 44 | struct imsgbuf { 45 | struct msgbuf *w; 46 | pid_t pid; 47 | uint32_t maxsize; 48 | int fd; 49 | int flags; 50 | }; 51 | 52 | struct imsg_hdr { 53 | uint32_t type; 54 | uint32_t len; 55 | uint32_t peerid; 56 | uint32_t pid; 57 | }; 58 | 59 | struct imsg { 60 | struct imsg_hdr hdr; 61 | void *data; 62 | struct ibuf *buf; 63 | }; 64 | 65 | struct iovec; 66 | 67 | /* imsg-buffer.c */ 68 | struct ibuf *ibuf_open(size_t); 69 | struct ibuf *ibuf_dynamic(size_t, size_t); 70 | int ibuf_add(struct ibuf *, const void *, size_t); 71 | int ibuf_add_ibuf(struct ibuf *, const struct ibuf *); 72 | int ibuf_add_zero(struct ibuf *, size_t); 73 | int ibuf_add_n8(struct ibuf *, uint64_t); 74 | int ibuf_add_n16(struct ibuf *, uint64_t); 75 | int ibuf_add_n32(struct ibuf *, uint64_t); 76 | int ibuf_add_n64(struct ibuf *, uint64_t); 77 | int ibuf_add_h16(struct ibuf *, uint64_t); 78 | int ibuf_add_h32(struct ibuf *, uint64_t); 79 | int ibuf_add_h64(struct ibuf *, uint64_t); 80 | void *ibuf_reserve(struct ibuf *, size_t); 81 | void *ibuf_seek(struct ibuf *, size_t, size_t); 82 | int ibuf_set(struct ibuf *, size_t, const void *, size_t); 83 | int ibuf_set_n8(struct ibuf *, size_t, uint64_t); 84 | int ibuf_set_n16(struct ibuf *, size_t, uint64_t); 85 | int ibuf_set_n32(struct ibuf *, size_t, uint64_t); 86 | int ibuf_set_n64(struct ibuf *, size_t, uint64_t); 87 | int ibuf_set_h16(struct ibuf *, size_t, uint64_t); 88 | int ibuf_set_h32(struct ibuf *, size_t, uint64_t); 89 | int ibuf_set_h64(struct ibuf *, size_t, uint64_t); 90 | void *ibuf_data(const struct ibuf *); 91 | size_t ibuf_size(const struct ibuf *); 92 | size_t ibuf_left(const struct ibuf *); 93 | int ibuf_truncate(struct ibuf *, size_t); 94 | void ibuf_rewind(struct ibuf *); 95 | void ibuf_close(struct msgbuf *, struct ibuf *); 96 | void ibuf_from_buffer(struct ibuf *, void *, size_t); 97 | void ibuf_from_ibuf(struct ibuf *, const struct ibuf *); 98 | int ibuf_get(struct ibuf *, void *, size_t); 99 | int ibuf_get_ibuf(struct ibuf *, size_t, struct ibuf *); 100 | int ibuf_get_n8(struct ibuf *, uint8_t *); 101 | int ibuf_get_n16(struct ibuf *, uint16_t *); 102 | int ibuf_get_n32(struct ibuf *, uint32_t *); 103 | int ibuf_get_n64(struct ibuf *, uint64_t *); 104 | int ibuf_get_h16(struct ibuf *, uint16_t *); 105 | int ibuf_get_h32(struct ibuf *, uint32_t *); 106 | int ibuf_get_h64(struct ibuf *, uint64_t *); 107 | char *ibuf_get_string(struct ibuf *, size_t); 108 | int ibuf_skip(struct ibuf *, size_t); 109 | void ibuf_free(struct ibuf *); 110 | int ibuf_fd_avail(struct ibuf *); 111 | int ibuf_fd_get(struct ibuf *); 112 | void ibuf_fd_set(struct ibuf *, int); 113 | struct msgbuf *msgbuf_new(void); 114 | struct msgbuf *msgbuf_new_reader(size_t, 115 | struct ibuf *(*)(struct ibuf *, void *, int *), void *); 116 | void msgbuf_free(struct msgbuf *); 117 | void msgbuf_clear(struct msgbuf *); 118 | uint32_t msgbuf_queuelen(struct msgbuf *); 119 | int ibuf_write(int, struct msgbuf *); 120 | int msgbuf_write(int, struct msgbuf *); 121 | int ibuf_read(int, struct msgbuf *); 122 | int msgbuf_read(int, struct msgbuf *); 123 | struct ibuf *msgbuf_get(struct msgbuf *); 124 | 125 | /* imsg.c */ 126 | int imsgbuf_init(struct imsgbuf *, int); 127 | void imsgbuf_allow_fdpass(struct imsgbuf *imsgbuf); 128 | int imsgbuf_set_maxsize(struct imsgbuf *, uint32_t); 129 | int imsgbuf_read(struct imsgbuf *); 130 | int imsgbuf_write(struct imsgbuf *); 131 | int imsgbuf_flush(struct imsgbuf *); 132 | void imsgbuf_clear(struct imsgbuf *); 133 | uint32_t imsgbuf_queuelen(struct imsgbuf *); 134 | ssize_t imsg_get(struct imsgbuf *, struct imsg *); 135 | int imsg_get_ibuf(struct imsg *, struct ibuf *); 136 | int imsg_get_data(struct imsg *, void *, size_t); 137 | int imsg_get_fd(struct imsg *); 138 | uint32_t imsg_get_id(struct imsg *); 139 | size_t imsg_get_len(struct imsg *); 140 | pid_t imsg_get_pid(struct imsg *); 141 | uint32_t imsg_get_type(struct imsg *); 142 | int imsg_forward(struct imsgbuf *, struct imsg *); 143 | int imsg_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, 144 | const void *, size_t); 145 | int imsg_composev(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, 146 | const struct iovec *, int); 147 | int imsg_compose_ibuf(struct imsgbuf *, uint32_t, uint32_t, pid_t, 148 | struct ibuf *); 149 | struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, size_t); 150 | int imsg_add(struct ibuf *, const void *, size_t); 151 | void imsg_close(struct imsgbuf *, struct ibuf *); 152 | void imsg_free(struct imsg *); 153 | 154 | #endif 155 | -------------------------------------------------------------------------------- /compat/memmem.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2005 Pascal Gloor 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in 11 | * the documentation and/or other materials provided with the 12 | * distribution. 13 | * 3. The name of the author may not be used to endorse or promote 14 | * products derived from this software without specific prior written 15 | * permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 19 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR 21 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | 32 | /* 33 | * Find the first occurrence of the byte string s in byte string l. 34 | */ 35 | void * 36 | memmem(const void *l, size_t l_len, const void *s, size_t s_len) 37 | { 38 | const char *cur, *last; 39 | const char *cl = l; 40 | const char *cs = s; 41 | 42 | /* a zero length needle should just return the haystack */ 43 | if (l_len == 0) 44 | return (void *)cl; 45 | 46 | /* "s" must be smaller or equal to "l" */ 47 | if (l_len < s_len) 48 | return NULL; 49 | 50 | /* special case where s_len == 1 */ 51 | if (s_len == 1) 52 | return memchr(l, *cs, l_len); 53 | 54 | /* the last position where its possible to find "s" in "l" */ 55 | last = cl + l_len - s_len; 56 | 57 | for (cur = cl; cur <= last; cur++) 58 | if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) 59 | return (void *)cur; 60 | 61 | return NULL; 62 | } 63 | -------------------------------------------------------------------------------- /compat/memrchr.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: memrchr.c,v 1.4 2019/01/25 00:19:25 millert Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2007 Todd C. Miller 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | 21 | /* 22 | * Reverse memchr() 23 | * Find the last occurrence of 'c' in the buffer 's' of size 'n'. 24 | */ 25 | void * 26 | memrchr(const void *s, int c, size_t n) 27 | { 28 | const unsigned char *cp; 29 | 30 | if (n != 0) { 31 | cp = (unsigned char *)s + n; 32 | do { 33 | if (*(--cp) == (unsigned char)c) 34 | return((void *)cp); 35 | } while (--n != 0); 36 | } 37 | return(NULL); 38 | } 39 | -------------------------------------------------------------------------------- /compat/reallocarray.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: reallocarray.c,v 1.3 2015/09/13 08:31:47 guenther Exp $ */ 2 | /* 3 | * Copyright (c) 2008 Otto Moerbeek 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include "config.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | /* 26 | * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX 27 | * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW 28 | */ 29 | #define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) 30 | 31 | void * 32 | reallocarray(void *optr, size_t nmemb, size_t size) 33 | { 34 | if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 35 | nmemb > 0 && SIZE_MAX / nmemb < size) { 36 | errno = ENOMEM; 37 | return NULL; 38 | } 39 | return realloc(optr, size * nmemb); 40 | } 41 | -------------------------------------------------------------------------------- /compat/recallocarray.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008, 2017 Otto Moerbeek 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | /* 26 | * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX 27 | * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW 28 | */ 29 | #define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) 30 | 31 | void * 32 | recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) 33 | { 34 | size_t oldsize, newsize; 35 | void *newptr; 36 | 37 | if (ptr == NULL) 38 | return calloc(newnmemb, size); 39 | 40 | if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 41 | newnmemb > 0 && SIZE_MAX / newnmemb < size) { 42 | errno = ENOMEM; 43 | return NULL; 44 | } 45 | newsize = newnmemb * size; 46 | 47 | if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 48 | oldnmemb > 0 && SIZE_MAX / oldnmemb < size) { 49 | errno = EINVAL; 50 | return NULL; 51 | } 52 | oldsize = oldnmemb * size; 53 | 54 | /* 55 | * Don't bother too much if we're shrinking just a bit, 56 | * we do not shrink for series of small steps, oh well. 57 | */ 58 | if (newsize <= oldsize) { 59 | size_t d = oldsize - newsize; 60 | 61 | if (d < oldsize / 2 && d < (size_t)getpagesize()) { 62 | memset((char *)ptr + newsize, 0, d); 63 | return ptr; 64 | } 65 | } 66 | 67 | newptr = malloc(newsize); 68 | if (newptr == NULL) 69 | return NULL; 70 | 71 | if (newsize > oldsize) { 72 | memcpy(newptr, ptr, oldsize); 73 | memset((char *)newptr + oldsize, 0, newsize - oldsize); 74 | } else 75 | memcpy(newptr, ptr, newsize); 76 | 77 | explicit_bzero(ptr, oldsize); 78 | free(ptr); 79 | 80 | return newptr; 81 | } 82 | -------------------------------------------------------------------------------- /compat/setproctitle.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Nicholas Marriott 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 13 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 14 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include "config.h" 18 | 19 | #include 20 | 21 | # if HAVE_PR_SET_NAME 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | void 28 | setproctitle(const char *fmt, ...) 29 | { 30 | char title[16], name[16], *cp; 31 | va_list ap; 32 | int used; 33 | 34 | va_start(ap, fmt); 35 | vsnprintf(title, sizeof(title), fmt, ap); 36 | va_end(ap); 37 | 38 | used = snprintf(name, sizeof(name), "%s: %s", getprogname(), title); 39 | if (used >= (int)sizeof(name)) { 40 | cp = strrchr(name, ' '); 41 | if (cp != NULL) 42 | *cp = '\0'; 43 | } 44 | prctl(PR_SET_NAME, name); 45 | } 46 | # else 47 | void 48 | setproctitle(const char *fmt, ...) 49 | { 50 | return; 51 | } 52 | # endif 53 | -------------------------------------------------------------------------------- /compat/sha1.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: sha1.c,v 1.27 2019/06/07 22:56:36 dtucker Exp $ */ 2 | 3 | /* 4 | * SHA-1 in C 5 | * By Steve Reid 6 | * 100% Public Domain 7 | * 8 | * Test Vectors (from FIPS PUB 180-1) 9 | * "abc" 10 | * A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D 11 | * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 12 | * 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 13 | * A million repetitions of "a" 14 | * 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) 22 | 23 | /* 24 | * blk0() and blk() perform the initial expand. 25 | * I got the idea of expanding during the round function from SSLeay 26 | */ 27 | #if BYTE_ORDER == LITTLE_ENDIAN 28 | # define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ 29 | |(rol(block->l[i],8)&0x00FF00FF)) 30 | #else 31 | # define blk0(i) block->l[i] 32 | #endif 33 | #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ 34 | ^block->l[(i+2)&15]^block->l[i&15],1)) 35 | 36 | /* 37 | * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 38 | */ 39 | #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); 40 | #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); 41 | #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); 42 | #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); 43 | #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); 44 | 45 | typedef union { 46 | u_int8_t c[64]; 47 | u_int32_t l[16]; 48 | } CHAR64LONG16; 49 | 50 | /* 51 | * Hash a single 512-bit block. This is the core of the algorithm. 52 | */ 53 | void 54 | SHA1Transform(u_int32_t state[5], const u_int8_t buffer[SHA1_BLOCK_LENGTH]) 55 | { 56 | u_int32_t a, b, c, d, e; 57 | u_int8_t workspace[SHA1_BLOCK_LENGTH]; 58 | CHAR64LONG16 *block = (CHAR64LONG16 *)workspace; 59 | 60 | (void)memcpy(block, buffer, SHA1_BLOCK_LENGTH); 61 | 62 | /* Copy context->state[] to working vars */ 63 | a = state[0]; 64 | b = state[1]; 65 | c = state[2]; 66 | d = state[3]; 67 | e = state[4]; 68 | 69 | /* 4 rounds of 20 operations each. Loop unrolled. */ 70 | R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); 71 | R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); 72 | R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); 73 | R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); 74 | R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); 75 | R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); 76 | R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); 77 | R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); 78 | R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); 79 | R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); 80 | R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); 81 | R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); 82 | R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); 83 | R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); 84 | R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); 85 | R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); 86 | R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); 87 | R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); 88 | R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); 89 | R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); 90 | 91 | /* Add the working vars back into context.state[] */ 92 | state[0] += a; 93 | state[1] += b; 94 | state[2] += c; 95 | state[3] += d; 96 | state[4] += e; 97 | 98 | /* Wipe variables */ 99 | a = b = c = d = e = 0; 100 | } 101 | 102 | 103 | /* 104 | * SHA1Init - Initialize new context 105 | */ 106 | void 107 | SHA1Init(SHA1_CTX *context) 108 | { 109 | 110 | /* SHA1 initialization constants */ 111 | context->count = 0; 112 | context->state[0] = 0x67452301; 113 | context->state[1] = 0xEFCDAB89; 114 | context->state[2] = 0x98BADCFE; 115 | context->state[3] = 0x10325476; 116 | context->state[4] = 0xC3D2E1F0; 117 | } 118 | 119 | 120 | /* 121 | * Run your data through this. 122 | */ 123 | void 124 | SHA1Update(SHA1_CTX *context, const u_int8_t *data, size_t len) 125 | { 126 | size_t i, j; 127 | 128 | j = (size_t)((context->count >> 3) & 63); 129 | context->count += ((u_int64_t)len << 3); 130 | if ((j + len) > 63) { 131 | (void)memcpy(&context->buffer[j], data, (i = 64-j)); 132 | SHA1Transform(context->state, context->buffer); 133 | for ( ; i + 63 < len; i += 64) 134 | SHA1Transform(context->state, (u_int8_t *)&data[i]); 135 | j = 0; 136 | } else { 137 | i = 0; 138 | } 139 | (void)memcpy(&context->buffer[j], &data[i], len - i); 140 | } 141 | 142 | 143 | /* 144 | * Add padding and return the message digest. 145 | */ 146 | void 147 | SHA1Pad(SHA1_CTX *context) 148 | { 149 | u_int8_t finalcount[8]; 150 | u_int i; 151 | 152 | for (i = 0; i < 8; i++) { 153 | finalcount[i] = (u_int8_t)((context->count >> 154 | ((7 - (i & 7)) * 8)) & 255); /* Endian independent */ 155 | } 156 | SHA1Update(context, (u_int8_t *)"\200", 1); 157 | while ((context->count & 504) != 448) 158 | SHA1Update(context, (u_int8_t *)"\0", 1); 159 | SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ 160 | } 161 | 162 | void 163 | SHA1Final(u_int8_t digest[SHA1_DIGEST_LENGTH], SHA1_CTX *context) 164 | { 165 | u_int i; 166 | 167 | SHA1Pad(context); 168 | for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { 169 | digest[i] = (u_int8_t) 170 | ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); 171 | } 172 | explicit_bzero(context, sizeof(*context)); 173 | } 174 | -------------------------------------------------------------------------------- /compat/sha1/sha1.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: sha1.h,v 1.24 2012/12/05 23:19:57 deraadt Exp $ */ 2 | 3 | /* 4 | * SHA-1 in C 5 | * By Steve Reid 6 | * 100% Public Domain 7 | */ 8 | 9 | #ifndef _SHA1_H 10 | #define _SHA1_H 11 | 12 | #define SHA1_BLOCK_LENGTH 64 13 | #define SHA1_DIGEST_LENGTH 20 14 | #define SHA1_DIGEST_STRING_LENGTH (SHA1_DIGEST_LENGTH * 2 + 1) 15 | 16 | typedef struct { 17 | u_int32_t state[5]; 18 | u_int64_t count; 19 | u_int8_t buffer[SHA1_BLOCK_LENGTH]; 20 | } SHA1_CTX; 21 | 22 | __BEGIN_DECLS 23 | void SHA1Init(SHA1_CTX *); 24 | void SHA1Pad(SHA1_CTX *); 25 | void SHA1Transform(u_int32_t [5], const u_int8_t [SHA1_BLOCK_LENGTH]) 26 | __attribute__((__bounded__(__minbytes__,1,5))) 27 | __attribute__((__bounded__(__minbytes__,2,SHA1_BLOCK_LENGTH))); 28 | void SHA1Update(SHA1_CTX *, const u_int8_t *, size_t) 29 | __attribute__((__bounded__(__string__,2,3))); 30 | void SHA1Final(u_int8_t [SHA1_DIGEST_LENGTH], SHA1_CTX *) 31 | __attribute__((__bounded__(__minbytes__,1,SHA1_DIGEST_LENGTH))); 32 | char *SHA1End(SHA1_CTX *, char *) 33 | __attribute__((__bounded__(__minbytes__,2,SHA1_DIGEST_STRING_LENGTH))); 34 | char *SHA1File(const char *, char *) 35 | __attribute__((__bounded__(__minbytes__,2,SHA1_DIGEST_STRING_LENGTH))); 36 | char *SHA1FileChunk(const char *, char *, off_t, off_t) 37 | __attribute__((__bounded__(__minbytes__,2,SHA1_DIGEST_STRING_LENGTH))); 38 | char *SHA1Data(const u_int8_t *, size_t, char *) 39 | __attribute__((__bounded__(__string__,1,2))) 40 | __attribute__((__bounded__(__minbytes__,3,SHA1_DIGEST_STRING_LENGTH))); 41 | __END_DECLS 42 | 43 | #define HTONDIGEST(x) do { \ 44 | x[0] = htonl(x[0]); \ 45 | x[1] = htonl(x[1]); \ 46 | x[2] = htonl(x[2]); \ 47 | x[3] = htonl(x[3]); \ 48 | x[4] = htonl(x[4]); } while (0) 49 | 50 | #define NTOHDIGEST(x) do { \ 51 | x[0] = ntohl(x[0]); \ 52 | x[1] = ntohl(x[1]); \ 53 | x[2] = ntohl(x[2]); \ 54 | x[3] = ntohl(x[3]); \ 55 | x[4] = ntohl(x[4]); } while (0) 56 | 57 | #endif /* _SHA1_H */ 58 | -------------------------------------------------------------------------------- /compat/stdio.h: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include_next "stdio.h" 3 | -------------------------------------------------------------------------------- /compat/stdlib.h: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include_next "stdlib.h" 3 | 4 | #if !HAVE_FREEZERO 5 | void freezero(void *, size_t); 6 | #endif 7 | 8 | #if !HAVE_GETPROGNAME 9 | const char *getprogname(void); 10 | #endif 11 | 12 | #if !HAVE_REALLOCARRAY 13 | void *reallocarray(void *, size_t, size_t); 14 | #endif 15 | 16 | #if !HAVE_RECALLOCARRAY 17 | void *recallocarray(void *, size_t, size_t, size_t); 18 | #endif 19 | 20 | #if !HAVE_SETPROCTITLE 21 | void setproctitle(const char *, ...); 22 | #endif 23 | 24 | #if !HAVE_STRTONUM 25 | long long strtonum(const char *, long long, long long, const char **); 26 | #endif 27 | -------------------------------------------------------------------------------- /compat/string.h: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include_next "string.h" 3 | 4 | #if !HAVE_EXPLICIT_BZERO 5 | void explicit_bzero(void *, size_t); 6 | #endif 7 | 8 | #if !HAVE_MEMMEM 9 | void *memmem(const void *, size_t, const void *, size_t); 10 | #endif 11 | 12 | #if !HAVE_MEMRCHR 13 | void *memrchr(const void *, int, size_t); 14 | #endif 15 | 16 | #if !HAVE_STRLCAT 17 | size_t strlcat(char *, const char *, size_t); 18 | #endif 19 | 20 | #if !HAVE_STRLCPY 21 | size_t strlcpy(char *, const char *, size_t); 22 | #endif 23 | -------------------------------------------------------------------------------- /compat/strlcat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1998 Todd C. Miller 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | 22 | /* 23 | * Appends src to string dst of size siz (unlike strncat, siz is the 24 | * full size of dst, not space left). At most siz-1 characters 25 | * will be copied. Always NUL terminates (unless siz <= strlen(dst)). 26 | * Returns strlen(src) + MIN(siz, strlen(initial dst)). 27 | * If retval >= siz, truncation occurred. 28 | */ 29 | size_t 30 | strlcat(char *dst, const char *src, size_t siz) 31 | { 32 | char *d = dst; 33 | const char *s = src; 34 | size_t n = siz; 35 | size_t dlen; 36 | 37 | /* Find the end of dst and adjust bytes left but don't go past end */ 38 | while (n-- != 0 && *d != '\0') 39 | d++; 40 | dlen = d - dst; 41 | n = siz - dlen; 42 | 43 | if (n == 0) 44 | return(dlen + strlen(s)); 45 | while (*s != '\0') { 46 | if (n != 1) { 47 | *d++ = *s; 48 | n--; 49 | } 50 | s++; 51 | } 52 | *d = '\0'; 53 | 54 | return(dlen + (s - src)); /* count does not include NUL */ 55 | } 56 | -------------------------------------------------------------------------------- /compat/strlcpy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1998 Todd C. Miller 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include "config.h" 18 | 19 | #include 20 | #include 21 | 22 | /* 23 | * Copy src to string dst of size siz. At most siz-1 characters 24 | * will be copied. Always NUL terminates (unless siz == 0). 25 | * Returns strlen(src); if retval >= siz, truncation occurred. 26 | */ 27 | size_t 28 | strlcpy(char *dst, const char *src, size_t siz) 29 | { 30 | char *d = dst; 31 | const char *s = src; 32 | size_t n = siz; 33 | 34 | /* Copy as many bytes as will fit */ 35 | if (n != 0) { 36 | while (--n != 0) { 37 | if ((*d++ = *s++) == '\0') 38 | break; 39 | } 40 | } 41 | 42 | /* Not enough room in dst, add NUL and traverse rest of src */ 43 | if (n == 0) { 44 | if (siz != 0) 45 | *d = '\0'; /* NUL-terminate dst */ 46 | while (*s++) 47 | ; 48 | } 49 | 50 | return(s - src - 1); /* count does not include NUL */ 51 | } 52 | -------------------------------------------------------------------------------- /compat/strtonum.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2004 Ted Unangst and Todd Miller 3 | * All rights reserved. 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include "config.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #define INVALID 1 25 | #define TOOSMALL 2 26 | #define TOOLARGE 3 27 | 28 | long long 29 | strtonum(const char *numstr, long long minval, long long maxval, 30 | const char **errstrp) 31 | { 32 | long long ll = 0; 33 | int error = 0; 34 | char *ep; 35 | struct errval { 36 | const char *errstr; 37 | int err; 38 | } ev[4] = { 39 | { NULL, 0 }, 40 | { "invalid", EINVAL }, 41 | { "too small", ERANGE }, 42 | { "too large", ERANGE }, 43 | }; 44 | 45 | ev[0].err = errno; 46 | errno = 0; 47 | if (minval > maxval) { 48 | error = INVALID; 49 | } else { 50 | ll = strtoll(numstr, &ep, 10); 51 | if (numstr == ep || *ep != '\0') 52 | error = INVALID; 53 | else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) 54 | error = TOOSMALL; 55 | else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) 56 | error = TOOLARGE; 57 | } 58 | if (errstrp != NULL) 59 | *errstrp = ev[error].errstr; 60 | errno = ev[error].err; 61 | if (error) 62 | ll = 0; 63 | 64 | return (ll); 65 | } 66 | -------------------------------------------------------------------------------- /compat/sys/time.h: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include_next "sys/time.h" 3 | 4 | #if !HAVE_TIMESPECSUB 5 | struct timespec; 6 | void timespecsub(struct timespec *, struct timespec *, struct timespec *); 7 | #endif 8 | -------------------------------------------------------------------------------- /compat/timespecsub.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | */ 9 | 10 | #include "config.h" 11 | 12 | #include 13 | 14 | void 15 | timespecsub(struct timespec *a, struct timespec *b, struct timespec *ret) 16 | { 17 | ret->tv_sec = a->tv_sec - b->tv_sec; 18 | ret->tv_nsec = a->tv_nsec - b->tv_nsec; 19 | if (ret->tv_nsec < 0) { 20 | ret->tv_sec--; 21 | ret->tv_nsec += 1000000000L; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /compat/unistd.h: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include_next "unistd.h" 3 | 4 | #if !HAVE_GETDTABLECOUNT 5 | /* XXX: on linux it should be possible to inspect /proc/self/fd/ */ 6 | #define getdtablecount() (0) 7 | #endif 8 | 9 | #if !HAVE_GETDTABLESIZE 10 | #define getdtablesize() (sysconf(_SC_OPEN_MAX)) 11 | #endif 12 | 13 | #if !HAVE_OPTRESET 14 | /* replace host' getopt with OpenBSD' one */ 15 | #define opterr BSDopterr 16 | #define optind BSDoptind 17 | #define optopt BSDoptopt 18 | #define optreset BSDoptreset 19 | #define optarg BSDoptarg 20 | #define getopt BSDgetopt 21 | 22 | extern int BSDopterr, BSDoptind, BSDoptopt, BSDoptreset; 23 | extern char *BSDoptarg; 24 | int BSDgetopt(int, char * const *, const char *); 25 | #endif 26 | -------------------------------------------------------------------------------- /configure.local.example: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2022 Omar Polo 3 | # Copyright (c) 2014-2022 Ingo Schwarze 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | # For all settings documented in this file, there are reasonable 18 | # defaults and/or the ./configure script attempts autodetection. 19 | # Consequently, you only need to create a file ./configure.local 20 | # and put any of these settings into it if ./configure autodetection 21 | # fails or if you want to make different choices for other reasons. 22 | 23 | # If autodetection fails, please tell . 24 | 25 | # We recommend that you write ./configure.local from scratch and 26 | # only put the lines there you need. This file contains examples. 27 | # It is not intended as a template to be copied as a whole. 28 | 29 | # Some systems may want to set additional linker flags for all the 30 | # binaries, for example for hardening options. 31 | 32 | LDFLAGS="-Wl,-z,relro" 33 | 34 | # It may be needed to change the flags for the individual libraries. 35 | 36 | LDADD_IMSG="-limsg" 37 | LDADD_LIBEVENT2="-L/opt/lib/ -levent_extra -levent_core" 38 | LDADD_LIB_FLAC= 39 | LDADD_LIB_MPG123= 40 | LDADD_LIB_VORBISFILE= 41 | LDADD_LIB_SNDIO= 42 | LDADD_LIB_SOCKET= 43 | 44 | # To disable the autoconfiguration via pkg-config set PKG_CONFIG to 45 | # the empty string: 46 | 47 | PKG_CONFIG= 48 | 49 | # It is possible to change the utility program used for installation 50 | # and the modes files are installed with. The defaults are: 51 | 52 | INSTALL="install" 53 | INSTALL_PROGRAM="${INSTALL} -m 0555" 54 | INSTALL_LIB="${INSTALL} -m 0444" 55 | INSTALL_MAN="${INSTALL} -m 0444" 56 | INSTALL_DATA="${INSTALL} -m 0444" 57 | 58 | # In rare cases, it may be required to skip individual automatic tests. 59 | # Each of the following variables can be set to 0 (test will not be run 60 | # and will be regarded as failed) or 1 (test will not be run and will 61 | # be regarded as successful). 62 | 63 | HAVE_CAPSICUM=0 64 | HAVE_ERR=0 65 | HAVE_EXPLICIT_BZERO=0 66 | HAVE_GETEXECNAME=0 67 | HAVE_GETPROGNAME=0 68 | HAVE_IMSG=0 69 | HAVE_LANDLOCK=0 70 | HAVE_LIBEVENT=0 71 | HAVE_LIBEVENT2=0 72 | HAVE_LIB_FLAC=0 73 | HAVE_LIB_MPG123=0 74 | HAVE_LIB_OPUSFILE=0 75 | HAVE_LIB_VORBISFILE=0 76 | HAVE_MEMMEM=0 77 | HAVE_MEMRCHR=0 78 | HAVE_PATH_MAX=0 79 | HAVE_PLEDGE=0 80 | HAVE_PROGRAM_INVOCATION_SHORT_NAME=0 81 | HAVE_REALLOCARRAY=0 82 | HAVE_RECALLOCARRAY=0 83 | HAVE_SANDBOX_INIT=0 84 | HAVE_STRLCAT=0 85 | HAVE_STRLCPY=0 86 | HAVE_STRNDUP=0 87 | HAVE_STRNLEN=0 88 | HAVE_STRTONUM=0 89 | HAVE_SYS_QUEUE=0 90 | HAVE_UNVEIL=0 91 | HAVE___PROGNAME=0 92 | -------------------------------------------------------------------------------- /contrib/README.md: -------------------------------------------------------------------------------- 1 | # amused/contrib 2 | 3 | Some contrib scripts. Additions are welcome! 4 | 5 | Scripts: 6 | 7 | - `amused-monitor`: a simple TUI written in perl. It depends on: 8 | * p5-Curses 9 | * p5-Text-CharWidth 10 | -------------------------------------------------------------------------------- /contrib/amused-termux-notification: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | amused="${AMUSED:-amused}" 4 | 5 | notify() { 6 | path="$($amused status -f path)" 7 | base="${path##*/}" 8 | name="${base%.*}" 9 | 10 | termux-notification -i amused --ongoing -t "$name" -c "$path" \ 11 | --type media \ 12 | --media-next "$amused next" \ 13 | --media-pause "$amused pause" \ 14 | --media-play "$amused play" \ 15 | --media-previous "$amused previous" 16 | } 17 | 18 | notify 19 | $amused monitor jump,next,pause,play,previous,stop | while read line; do 20 | notify 21 | done 22 | -------------------------------------------------------------------------------- /contrib/amusing: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tclsh8.6 2 | # 3 | # Copyright (c) 2023 Omar Polo 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | package require Tk 18 | 19 | set usongs {} ;# unfiltered sogs 20 | set fsongs {} ;# filtered songs 21 | set query "" 22 | set cur 0 23 | set max 0 24 | set cur_song "" 25 | set cur_time 0 26 | set max_time 0 27 | set mode {"off" "off" "off"} 28 | 29 | set toggle_btn "pause" 30 | 31 | # workaround for spurious first event from ttk::scale 32 | set first_seek yes 33 | 34 | proc amused_jump {song} { 35 | puts "jumping to $song" 36 | exec amused jump $song 37 | } 38 | 39 | proc amused_seek {pos} { 40 | global cur_time first_seek 41 | 42 | if {$first_seek == yes} { 43 | puts "skipping spurious seek" 44 | set first_seek no 45 | return 46 | } 47 | 48 | set pos [expr {round($pos)}] 49 | set tmp [showtime $pos] 50 | 51 | if {$cur_time != $tmp} { 52 | set cur_time $tmp 53 | puts "seeking to $cur_time" 54 | exec amused seek $pos 55 | } 56 | } 57 | 58 | proc amused {cmd} { 59 | puts "exec amused $cmd" 60 | exec "amused" $cmd 61 | } 62 | 63 | proc getsongs {} { 64 | global usongs 65 | global cur 66 | global cur_song 67 | global max 68 | global query 69 | 70 | set usongs {} 71 | 72 | set fd [open "|amused show -p"] 73 | 74 | set i 0 75 | while {[gets $fd line] != -1} { 76 | set marker [string range $line 0 1] 77 | if {$marker == "> "} { 78 | set cur $i 79 | # XXX: is wrong to do that here. 80 | set cur_song [string range $line 2 end] 81 | } 82 | 83 | set song [string range $line 2 end] 84 | set usongs [lappend usongs $song] 85 | 86 | incr i 87 | } 88 | 89 | set max $i 90 | dofilter $query 91 | .c.main.list see $cur 92 | 93 | close $fd 94 | } 95 | 96 | proc dofilter {query} { 97 | global usongs fsongs cur cur_song 98 | 99 | set q [string tolower [string trim $query]] 100 | set fsongs {} 101 | set i -1 102 | foreach e $usongs { 103 | set l [string tolower $e] 104 | if {$q == "" || [string first $q $l] != -1} { 105 | incr i 106 | set fsongs [lappend fentries $e] 107 | if {$e == $cur_song} { 108 | set cur $i 109 | } 110 | } 111 | } 112 | 113 | .c.main.list selection set $cur 114 | } 115 | 116 | proc updatefilter {varname args} { 117 | upvar #0 $varname var 118 | dofilter $var 119 | } 120 | 121 | proc settime {var text} { 122 | upvar $var time 123 | 124 | set parsed [split $text] 125 | set t [lindex $parsed 1] 126 | set time [showtime $t] 127 | } 128 | 129 | proc setmode {n m text} { 130 | global mode 131 | 132 | set parsed [split $text] 133 | set t [lindex $parsed $m] 134 | lset mode $n $t 135 | } 136 | 137 | proc getstatus {} { 138 | global cur_time 139 | global max_time 140 | global toggle_btn 141 | 142 | set fd [open "|amused status -f status,time:raw,mode"] 143 | 144 | while {[gets $fd line] != -1} { 145 | switch -glob $line { 146 | "playing *" { 147 | set toggle_btn "⏸" 148 | } 149 | "paused *" { 150 | set toggle_btn "⏵" 151 | } 152 | "stopped *" { 153 | set toggle_btn "⏵" 154 | } 155 | "position *" {settime cur_time $line} 156 | "duration *" {settime max_time $line} 157 | "repeat all *" {setmode 0 2 $line} 158 | "repeat one *" {setmode 1 2 $line} 159 | "consume *" {setmode 2 1 $line} 160 | } 161 | } 162 | 163 | close $fd 164 | } 165 | 166 | proc setpos {ev} { 167 | global cur_time max_time 168 | 169 | set t [split $ev] 170 | 171 | set cur_time [showtime [lindex $t 1]] 172 | set max_time [showtime [lindex $t 2]] 173 | 174 | .c.bottom.bar set [lindex $t 1] 175 | .c.bottom.bar configure -to [lindex $t 2] 176 | } 177 | 178 | proc handle_event {fd} { 179 | global toggle_btn 180 | 181 | if {[eof $fd]} { 182 | set loop 0 183 | } 184 | 185 | set ev [gets $fd] 186 | 187 | #puts "got event $ev" 188 | 189 | switch -glob $ev { 190 | "add *" {getsongs} 191 | "jump" {getsongs} 192 | "load" {getsongs} 193 | "mode *" {puts "TODO: refresh mode"} 194 | "next" {getsongs} # may be optimized 195 | "pause" { 196 | set toggle_btn "⏵" 197 | } 198 | "play" { 199 | set toggle_btn "⏸" 200 | } 201 | "previous" {getsongs} # may be optimized 202 | "seek *" {setpos $ev} 203 | "stop" { 204 | set toggle_btn "⏸" 205 | } 206 | default {puts "un-catched event $ev"} 207 | } 208 | } 209 | 210 | proc showtime {seconds} { 211 | set tmp "" 212 | if {$seconds > 3600} { 213 | set hours [expr {$seconds / 3600}] 214 | set seconds [expr {$seconds - $hours * 3600}] 215 | set tmp [format "%02d:" $hours] 216 | } 217 | 218 | set minutes [expr {$seconds / 60}] 219 | set seconds [expr {$seconds - $minutes * 60}] 220 | return [format "%s%02d:%02d" $tmp $minutes $seconds] 221 | } 222 | 223 | # start the gui 224 | 225 | option add *tearOff 0 226 | wm title . gamused 227 | wm geometry . 600x300 228 | 229 | # create and grid the outer content frame 230 | grid [ttk::frame .c -padding "5 5 5 5"] -column 0 -row 0 -sticky nsew 231 | 232 | grid [ttk::frame .c.top -padding "5 0 5 5"] 233 | ttk::entry .c.top.query -textvariable query -width 50 234 | trace add variable query write "updatefilter query" 235 | grid .c.top.query -column 0 -row 0 236 | 237 | grid [ttk::frame .c.main] -column 0 -row 1 -sticky nsew 238 | tk::listbox .c.main.list -listvariable fsongs \ 239 | -yscrollcommand ".c.main.scroll set" -exportselection no \ 240 | -selectbackground "#8888cc" -selectforeground "#ffffff" 241 | ttk::scrollbar .c.main.scroll -command ".c.main.list yview" -orient vertical 242 | 243 | grid .c.main.list -column 0 -row 0 -sticky nwes 244 | grid .c.main.scroll -column 1 -row 0 -sticky ns 245 | 246 | bind .c.main.list <> { 247 | set curselection [.c.main.list curselection] 248 | if {$curselection != ""} { 249 | amused_jump [lindex $fsongs $curselection] 250 | } else { 251 | # something strange happened. maybe lost focus. 252 | # set the current again. 253 | .c.main.list selection set $cur 254 | } 255 | } 256 | 257 | ttk::style configure ctrl.TButton -font {sans-serif 16} 258 | 259 | grid [ttk::frame .c.cntl -padding "0 5 0 5"] -column 0 -row 2 260 | ttk::button .c.cntl.prev -style ctrl.TButton -width 3 \ 261 | -text "⏮" -command "amused previous" 262 | ttk::button .c.cntl.togg -style ctrl.TButton -width 3 \ 263 | -textvariable toggle_btn -command "amused toggle" 264 | ttk::button .c.cntl.stop -style ctrl.TButton -width 3 \ 265 | -text "⏹" -command "amused stop" 266 | ttk::button .c.cntl.next -style ctrl.TButton -width 3 \ 267 | -text "⏭" -command "amused next" 268 | 269 | grid .c.cntl.prev -column 0 -row 0 270 | grid .c.cntl.togg -column 1 -row 0 271 | grid .c.cntl.stop -column 2 -row 0 272 | grid .c.cntl.next -column 3 -row 0 273 | 274 | grid [ttk::frame .c.bottom -borderwidth 2] -column 0 -row 3 275 | ttk::label .c.bottom.cur_time -textvariable cur_time -padding "0 0 5 0" 276 | ttk::scale .c.bottom.bar -orient horizontal -length 400 -command amused_seek 277 | ttk::label .c.bottom.max_time -textvariable max_time -padding "5 0 0 0" 278 | 279 | grid [ttk::frame .c.current -padding "0 5 0 0"] -column 0 -row 4 280 | grid [ttk::label .c.current.title -textvariable cur_song] 281 | 282 | grid .c.bottom.cur_time -column 0 -row 0 283 | grid .c.bottom.bar -column 1 -row 0 284 | grid .c.bottom.max_time -column 2 -row 0 285 | 286 | # make resizing works 287 | grid columnconfigure . 0 -weight 1 288 | grid rowconfigure . 0 -weight 1 289 | 290 | grid columnconfigure .c 0 -weight 1 291 | grid rowconfigure .c 1 -weight 1 292 | 293 | grid columnconfigure .c.main 0 -weight 1 294 | grid rowconfigure .c.main 0 -weight 1 295 | 296 | grid columnconfigure .c.bottom 1 -weight 1 297 | grid rowconfigure .c.bottom 0 -weight 1 298 | 299 | # define keybindings 300 | 301 | bind . {amused toggle} 302 | bind . {amused next} 303 | bind .

{amused previous} 304 | bind . {amused stop} 305 | bind . {exec amused seek -1} 306 | bind . {exec amused seek -5} 307 | bind . {exec amused seek +1} 308 | bind . {exec amused seek +5} 309 | bind . {focus .c.top.query} 310 | 311 | bind .c.top.query {focus .} 312 | 313 | bind . {exit} 314 | 315 | # init the state 316 | 317 | set fd [open "|amused monitor" r] 318 | fileevent $fd readable "handle_event $fd" 319 | 320 | getsongs 321 | getstatus 322 | 323 | #set loop 1 324 | #vwait loop 325 | -------------------------------------------------------------------------------- /control.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: control.h,v 1.3 2021/01/19 16:54:00 florian Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2003, 2004 Henning Brauer 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | int control_init(char *); 20 | int control_listen(int fd); 21 | void control_accept(int, int, void *); 22 | void control_notify(int); 23 | void control_dispatch_imsg(int, int, void *); 24 | -------------------------------------------------------------------------------- /ev.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | struct timeval; 27 | 28 | #define EV_READ 0x1 29 | #define EV_WRITE 0x2 30 | #define EV_SIGNAL 0x4 31 | #define EV_TIMEOUT 0x8 32 | 33 | int ev_init(void); 34 | int ev_add(int, int, void(*)(int, int, void *), void *); 35 | int ev_signal(int, void(*)(int, int, void * ), void *); 36 | unsigned int ev_timer(const struct timeval *, void(*)(int, int, void *), 37 | void *); 38 | int ev_timer_pending(unsigned int); 39 | int ev_timer_cancel(unsigned int); 40 | int ev_del(int); 41 | int ev_step(void); 42 | int ev_step(void); 43 | int ev_loop(void); 44 | void ev_break(void); 45 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: log.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2003, 2004 Henning Brauer 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "log.h" 28 | 29 | static int debug; 30 | static int verbose; 31 | static const char *log_procname; 32 | 33 | void 34 | log_init(int n_debug, int facility) 35 | { 36 | debug = n_debug; 37 | verbose = n_debug; 38 | log_procinit(getprogname()); 39 | 40 | if (!debug) 41 | openlog(getprogname(), LOG_PID | LOG_NDELAY, facility); 42 | 43 | tzset(); 44 | } 45 | 46 | void 47 | log_procinit(const char *procname) 48 | { 49 | if (procname != NULL) 50 | log_procname = procname; 51 | } 52 | 53 | void 54 | log_setverbose(int v) 55 | { 56 | verbose = v; 57 | } 58 | 59 | int 60 | log_getverbose(void) 61 | { 62 | return (verbose); 63 | } 64 | 65 | void 66 | logit(int pri, const char *fmt, ...) 67 | { 68 | va_list ap; 69 | 70 | va_start(ap, fmt); 71 | vlog(pri, fmt, ap); 72 | va_end(ap); 73 | } 74 | 75 | void 76 | vlog(int pri, const char *fmt, va_list ap) 77 | { 78 | char *nfmt; 79 | int saved_errno = errno; 80 | 81 | if (debug) { 82 | /* best effort in out of mem situations */ 83 | if (asprintf(&nfmt, "%s\n", fmt) == -1) { 84 | vfprintf(stderr, fmt, ap); 85 | fprintf(stderr, "\n"); 86 | } else { 87 | vfprintf(stderr, nfmt, ap); 88 | free(nfmt); 89 | } 90 | fflush(stderr); 91 | } else 92 | vsyslog(pri, fmt, ap); 93 | 94 | errno = saved_errno; 95 | } 96 | 97 | void 98 | log_warn(const char *emsg, ...) 99 | { 100 | char *nfmt; 101 | va_list ap; 102 | int saved_errno = errno; 103 | 104 | /* best effort to even work in out of memory situations */ 105 | if (emsg == NULL) 106 | logit(LOG_ERR, "%s", strerror(saved_errno)); 107 | else { 108 | va_start(ap, emsg); 109 | 110 | if (asprintf(&nfmt, "%s: %s", emsg, 111 | strerror(saved_errno)) == -1) { 112 | /* we tried it... */ 113 | vlog(LOG_ERR, emsg, ap); 114 | logit(LOG_ERR, "%s", strerror(saved_errno)); 115 | } else { 116 | vlog(LOG_ERR, nfmt, ap); 117 | free(nfmt); 118 | } 119 | va_end(ap); 120 | } 121 | 122 | errno = saved_errno; 123 | } 124 | 125 | void 126 | log_warnx(const char *emsg, ...) 127 | { 128 | va_list ap; 129 | 130 | va_start(ap, emsg); 131 | vlog(LOG_ERR, emsg, ap); 132 | va_end(ap); 133 | } 134 | 135 | void 136 | log_info(const char *emsg, ...) 137 | { 138 | va_list ap; 139 | 140 | va_start(ap, emsg); 141 | vlog(LOG_INFO, emsg, ap); 142 | va_end(ap); 143 | } 144 | 145 | void 146 | log_debug(const char *emsg, ...) 147 | { 148 | va_list ap; 149 | 150 | if (verbose) { 151 | va_start(ap, emsg); 152 | vlog(LOG_DEBUG, emsg, ap); 153 | va_end(ap); 154 | } 155 | } 156 | 157 | static void 158 | vfatalc(int code, const char *emsg, va_list ap) 159 | { 160 | static char s[BUFSIZ]; 161 | const char *sep; 162 | 163 | if (emsg != NULL) { 164 | (void)vsnprintf(s, sizeof(s), emsg, ap); 165 | sep = ": "; 166 | } else { 167 | s[0] = '\0'; 168 | sep = ""; 169 | } 170 | if (code) 171 | logit(LOG_CRIT, "fatal in %s: %s%s%s", 172 | log_procname, s, sep, strerror(code)); 173 | else 174 | logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s); 175 | } 176 | 177 | void 178 | fatal(const char *emsg, ...) 179 | { 180 | va_list ap; 181 | 182 | va_start(ap, emsg); 183 | vfatalc(errno, emsg, ap); 184 | va_end(ap); 185 | exit(1); 186 | } 187 | 188 | void 189 | fatalx(const char *emsg, ...) 190 | { 191 | va_list ap; 192 | 193 | va_start(ap, emsg); 194 | vfatalc(0, emsg, ap); 195 | va_end(ap); 196 | exit(1); 197 | } 198 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: log.h,v 1.2 2021/12/13 18:28:40 deraadt Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2003, 2004 Henning Brauer 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #ifndef LOG_H 20 | #define LOG_H 21 | 22 | #include 23 | 24 | void log_init(int, int); 25 | void log_procinit(const char *); 26 | void log_setverbose(int); 27 | int log_getverbose(void); 28 | void log_warn(const char *, ...) 29 | __attribute__((__format__ (printf, 1, 2))); 30 | void log_warnx(const char *, ...) 31 | __attribute__((__format__ (printf, 1, 2))); 32 | void log_info(const char *, ...) 33 | __attribute__((__format__ (printf, 1, 2))); 34 | void log_debug(const char *, ...) 35 | __attribute__((__format__ (printf, 1, 2))); 36 | void logit(int, const char *, ...) 37 | __attribute__((__format__ (printf, 2, 3))); 38 | void vlog(int, const char *, va_list) 39 | __attribute__((__format__ (printf, 2, 0))); 40 | __dead void fatal(const char *, ...) 41 | __attribute__((__format__ (printf, 1, 2))); 42 | __dead void fatalx(const char *, ...) 43 | __attribute__((__format__ (printf, 1, 2))); 44 | 45 | #endif /* LOG_H */ 46 | -------------------------------------------------------------------------------- /mpris2/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | PROG = amused-mpris2 4 | 5 | SOURCES = mpris2.c ../log.c ../xmalloc.c 6 | 7 | OBJS = ${SOURCES:.c=.o} ${COBJS:%=../compat/%} 8 | 9 | DISTFILES = Makefile amused-mpris2.1 mpris2.c spec.h 10 | 11 | TOPDIR = .. 12 | 13 | all: ${PROG} 14 | 15 | ../config.mk ../config.h: ../configure ../tests.c 16 | @echo "$@ is out of date; please run ../configure" 17 | @exit 1 18 | 19 | include ../config.mk 20 | 21 | # --- targets --- 22 | 23 | ${PROG}: ${OBJS} 24 | ${CC} -o $@ ${OBJS} ${LDFLAGS} ${LDADD} ${LDADD_LIB_IMSG} \ 25 | ${LDADD_LIB_GLIB} ${LDADD_LIB_SOCKET} ${LDADD_LIB_MD} 26 | 27 | clean: 28 | rm -f ${OBJS} ${OBJS:.o=.d} ${PROG} 29 | 30 | distclean: clean 31 | 32 | install: 33 | mkdir -p ${DESTDIR}${BINDIR} 34 | mkdir -p ${DESTDIR}${MANDIR}/man1 35 | ${INSTALL_PROGRAM} ${PROG} ${DESTDIR}${BINDIR} 36 | ${INSTALL_MAN} amused-mpris2.1 ${DESTDIR}${MANDIR}/man1/${PROG}.1 37 | 38 | install-local: 39 | mkdir -p ${HOME}/bin 40 | ${INSTALL_PROGRAM} ${PROG} ${HOME}/bin 41 | 42 | uninstall: 43 | rm ${DESTDIR}${BINDIR}/${PROG} 44 | rm ${DESTDIR}${MANDIR}/man1/${PROG}.1 45 | 46 | .c.o: 47 | ${CC} -I../ -I../compat ${CFLAGS} -c $< -o $@ 48 | 49 | # --- maintainer targets --- 50 | 51 | dist: 52 | mkdir -p ${DESTDIR}/ 53 | ${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/ 54 | 55 | # --- dependency management --- 56 | 57 | # these .d files are produced during the first build if the compiler 58 | # supports it. 59 | 60 | -include mpris.d 61 | -include ../log.d 62 | -include ../xmalloc.d 63 | -------------------------------------------------------------------------------- /mpris2/amused-mpris2.1: -------------------------------------------------------------------------------- 1 | .\" Copyright (c) 2024 Omar Polo 2 | .\" 3 | .\" Permission to use, copy, modify, and distribute this software for any 4 | .\" purpose with or without fee is hereby granted, provided that the above 5 | .\" copyright notice and this permission notice appear in all copies. 6 | .\" 7 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | .\" 15 | .Dd August 2, 2024 16 | .Dt AMUSED-MPRIS2 1 17 | .Os 18 | .Sh NAME 19 | .Nm amused-mpris2 20 | .Nd DBus control interface for amused 21 | .Sh SYNOPSIS 22 | .Nm 23 | .Op Fl dv 24 | .Op Fl s Ar socket 25 | .Sh DESCRIPTION 26 | .Nm 27 | is a DBus interface to control the 28 | .Xr amused 1 29 | music player. 30 | It exposes the 31 | .Dq standard 32 | MPRIS2 33 | .Pq Media Player Remote Interfacing Specification 34 | interface that DBus-capable program can use to query and control 35 | .Xr amused 1 . 36 | .Pp 37 | The following options are available: 38 | .Bl -tag -width tenletters 39 | .It Fl d 40 | Do not daemonize. 41 | .Nm 42 | will run in the foreground and log to standard error. 43 | .It Fl s Ar socket 44 | Path to the 45 | .Xr amused 1 46 | control socket. 47 | By default 48 | .Pa /tmp/amused-UID 49 | is used. 50 | .It Fl v 51 | Produce more verbose output. 52 | .El 53 | .Sh ENVIRONMENT 54 | .Bl -tag -width tenletters 55 | .It Ev TEMPDIR 56 | Path to the directory where the control socket looked for. 57 | Defaults to 58 | .Pa /tmp . 59 | .El 60 | .Sh SEE ALSO 61 | .Xr amused 1 62 | .Sh AUTHORS 63 | .An -nosplit 64 | The 65 | .Nm 66 | program was written by 67 | .An Omar Polo Aq Mt op@omarpolo.com . 68 | -------------------------------------------------------------------------------- /player.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, 2023 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "amused.h" 33 | #include "audio.h" 34 | #include "log.h" 35 | #include "player.h" 36 | #include "xmalloc.h" 37 | 38 | struct pollfd *player_pfds; 39 | int player_nfds; 40 | static struct imsgbuf *imsgbuf; 41 | 42 | static int nextfd = -1; 43 | static int64_t frames; 44 | static int64_t duration; 45 | static unsigned int current_rate; 46 | 47 | volatile sig_atomic_t halted; 48 | 49 | static void 50 | player_signal_handler(int signo) 51 | { 52 | halted = 1; 53 | } 54 | 55 | int 56 | player_setup(unsigned int bits, unsigned int rate, unsigned int channels) 57 | { 58 | log_debug("%s: bits=%u, rate=%u, channels=%u", __func__, 59 | bits, rate, channels); 60 | 61 | current_rate = rate; 62 | return audio_setup(bits, rate, channels, player_pfds + 1, player_nfds); 63 | } 64 | 65 | void 66 | player_setduration(int64_t d) 67 | { 68 | int64_t seconds; 69 | 70 | duration = d; 71 | seconds = duration / current_rate; 72 | imsg_compose(imsgbuf, IMSG_LEN, 0, 0, -1, &seconds, sizeof(seconds)); 73 | imsgbuf_flush(imsgbuf); 74 | } 75 | 76 | static void 77 | player_onmove(void *arg, int delta) 78 | { 79 | static int64_t reported; 80 | int64_t sec; 81 | 82 | frames += delta; 83 | if (llabs(frames - reported) >= current_rate) { 84 | reported = frames; 85 | sec = frames / current_rate; 86 | 87 | imsg_compose(imsgbuf, IMSG_POS, 0, 0, -1, &sec, sizeof(sec)); 88 | imsgbuf_flush(imsgbuf); 89 | } 90 | } 91 | 92 | void 93 | player_setpos(int64_t pos) 94 | { 95 | frames = pos; 96 | player_onmove(NULL, 0); 97 | } 98 | 99 | /* process only one message */ 100 | static int 101 | player_dispatch(int64_t *s, int wait) 102 | { 103 | struct player_seek seek; 104 | struct pollfd pfd; 105 | struct imsg imsg; 106 | ssize_t n; 107 | int ret; 108 | 109 | if (halted != 0) 110 | return IMSG_STOP; 111 | 112 | again: 113 | if ((n = imsg_get(imsgbuf, &imsg)) == -1) 114 | fatal("imsg_get"); 115 | if (n == 0) { 116 | if (!wait) 117 | return -1; 118 | 119 | pfd.fd = imsgbuf->fd; 120 | pfd.events = POLLIN; 121 | if (poll(&pfd, 1, INFTIM) == -1) 122 | fatal("poll"); 123 | if ((n = imsgbuf_read(imsgbuf)) == -1) 124 | fatal("imsg_read"); 125 | if (n == 0) 126 | fatalx("pipe closed"); 127 | goto again; 128 | } 129 | 130 | ret = imsg_get_type(&imsg); 131 | switch (ret) { 132 | case IMSG_PLAY: 133 | if (nextfd != -1) 134 | fatalx("track already enqueued"); 135 | if ((nextfd = imsg_get_fd(&imsg)) == -1) 136 | fatalx("%s: got invalid file descriptor", __func__); 137 | log_debug("song enqueued"); 138 | ret = IMSG_STOP; 139 | break; 140 | case IMSG_RESUME: 141 | case IMSG_PAUSE: 142 | case IMSG_STOP: 143 | break; 144 | case IMSG_CTL_SEEK: 145 | if (s == NULL) 146 | break; 147 | if (imsg_get_data(&imsg, &seek, sizeof(seek)) == -1) 148 | fatalx("wrong size for seek ctl"); 149 | if (seek.percent) 150 | *s = (double)seek.offset * (double)duration / 100.0; 151 | else 152 | *s = seek.offset * current_rate; 153 | if (seek.relative) 154 | *s += frames; 155 | if (*s < 0) 156 | *s = 0; 157 | break; 158 | default: 159 | fatalx("unknown imsg %d", ret); 160 | } 161 | 162 | imsg_free(&imsg); 163 | return ret; 164 | } 165 | 166 | static void 167 | player_senderr(const char *errstr) 168 | { 169 | size_t len = 0; 170 | 171 | if (errstr != NULL) 172 | len = strlen(errstr) + 1; 173 | 174 | imsg_compose(imsgbuf, IMSG_ERR, 0, 0, -1, errstr, len); 175 | imsgbuf_flush(imsgbuf); 176 | } 177 | 178 | static void 179 | player_sendeof(void) 180 | { 181 | imsg_compose(imsgbuf, IMSG_EOF, 0, 0, -1, NULL, 0); 182 | imsgbuf_flush(imsgbuf); 183 | } 184 | 185 | static int 186 | player_playnext(const char **errstr) 187 | { 188 | static char buf[512]; 189 | ssize_t r; 190 | int fd = nextfd; 191 | 192 | assert(nextfd != -1); 193 | nextfd = -1; 194 | 195 | /* reset frames and set position to zero */ 196 | frames = 0; 197 | imsg_compose(imsgbuf, IMSG_POS, 0, 0, -1, &frames, sizeof(frames)); 198 | imsgbuf_flush(imsgbuf); 199 | 200 | r = read(fd, buf, sizeof(buf)); 201 | 202 | /* 8 byte is the larger magic number */ 203 | if (r < 8) { 204 | *errstr = "read failed"; 205 | goto err; 206 | } 207 | 208 | if (lseek(fd, 0, SEEK_SET) == -1) { 209 | *errstr = "lseek failed"; 210 | goto err; 211 | } 212 | 213 | if (memcmp(buf, "fLaC", 4) == 0) 214 | return play_flac(fd, errstr); 215 | if (memcmp(buf, "ID3", 3) == 0 || 216 | memcmp(buf, "\xFF\xFB", 2) == 0) 217 | return play_mp3(fd, errstr); 218 | if (memmem(buf, r, "OpusHead", 8) != NULL) 219 | return play_opus(fd, errstr); 220 | if (memmem(buf, r, "OggS", 4) != NULL) 221 | return play_oggvorbis(fd, errstr); 222 | 223 | *errstr = "unknown file type"; 224 | err: 225 | close(fd); 226 | return -1; 227 | } 228 | 229 | static int 230 | player_pause(int64_t *s) 231 | { 232 | int r; 233 | 234 | r = player_dispatch(s, 1); 235 | return r == IMSG_RESUME || r == IMSG_CTL_SEEK; 236 | } 237 | 238 | static int 239 | player_shouldstop(int64_t *s, int wait) 240 | { 241 | switch (player_dispatch(s, wait)) { 242 | case IMSG_PAUSE: 243 | if (player_pause(s)) 244 | break; 245 | /* fallthrough */ 246 | case IMSG_STOP: 247 | return 1; 248 | } 249 | 250 | return 0; 251 | } 252 | 253 | int 254 | play(const void *buf, size_t len, int64_t *s) 255 | { 256 | size_t w; 257 | int revents, r, wait; 258 | 259 | *s = -1; 260 | while (len != 0) { 261 | audio_pollfd(player_pfds + 1, player_nfds, POLLOUT); 262 | r = poll(player_pfds, player_nfds + 1, INFTIM); 263 | if (r == -1) 264 | fatal("poll"); 265 | 266 | wait = player_pfds[0].revents & (POLLHUP|POLLIN); 267 | if (player_shouldstop(s, wait)) { 268 | audio_flush(); 269 | return 0; 270 | } 271 | 272 | revents = audio_revents(player_pfds + 1, player_nfds); 273 | if (revents & POLLHUP) { 274 | if (errno == EAGAIN) 275 | continue; 276 | fatal("audio hang-up"); 277 | } 278 | if (revents & POLLOUT) { 279 | w = audio_write(buf, len); 280 | len -= w; 281 | buf += w; 282 | } 283 | } 284 | 285 | return 1; 286 | } 287 | 288 | int 289 | player(int debug, int verbose) 290 | { 291 | int r; 292 | 293 | log_init(debug, LOG_DAEMON); 294 | log_setverbose(verbose); 295 | 296 | setproctitle("player"); 297 | log_procinit("player"); 298 | 299 | #if 0 300 | { 301 | static int attached; 302 | 303 | while (!attached) 304 | sleep(1); 305 | } 306 | #endif 307 | 308 | if (audio_open(player_onmove) == -1) 309 | fatal("audio_open"); 310 | 311 | if ((player_nfds = audio_nfds()) <= 0) 312 | fatal("audio_nfds: invalid number of file descriptors: %d", 313 | player_nfds); 314 | 315 | /* allocate one extra for imsg */ 316 | player_pfds = calloc(player_nfds + 1, sizeof(*player_pfds)); 317 | if (player_pfds == NULL) 318 | fatal("calloc"); 319 | 320 | player_pfds[0].events = POLLIN; 321 | player_pfds[0].fd = 3; 322 | 323 | imsgbuf = xmalloc(sizeof(*imsgbuf)); 324 | if (imsgbuf_init(imsgbuf, 3) == -1) 325 | fatal("imsgbuf_init"); 326 | imsgbuf_allow_fdpass(imsgbuf); 327 | 328 | signal(SIGINT, player_signal_handler); 329 | signal(SIGTERM, player_signal_handler); 330 | 331 | signal(SIGHUP, SIG_IGN); 332 | signal(SIGPIPE, SIG_IGN); 333 | 334 | if (pledge("stdio recvfd audio", NULL) == -1) 335 | fatal("pledge"); 336 | 337 | while (!halted) { 338 | const char *errstr = NULL; 339 | 340 | while (nextfd == -1) 341 | player_dispatch(NULL, 1); 342 | 343 | r = player_playnext(&errstr); 344 | if (r == -1) 345 | player_senderr(errstr); 346 | if (r == 0) 347 | player_sendeof(); 348 | } 349 | 350 | return 0; 351 | } 352 | -------------------------------------------------------------------------------- /player.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, 2023 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef AMUSED_BUFSIZ 18 | #define AMUSED_BUFSIZ (16 * 1024) 19 | #endif 20 | 21 | int player_setup(unsigned int, unsigned int, unsigned int); 22 | void player_setduration(int64_t); 23 | void player_setpos(int64_t); 24 | int play(const void *, size_t, int64_t *); 25 | int player(int, int); 26 | 27 | int play_oggvorbis(int, const char **); 28 | int play_mp3(int, const char **); 29 | int play_flac(int, const char **); 30 | int play_opus(int, const char **); 31 | -------------------------------------------------------------------------------- /player_123.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include "log.h" 27 | #include "player.h" 28 | 29 | static int 30 | setup(mpg123_handle *mh) 31 | { 32 | long rate; 33 | int chan, enc; 34 | 35 | if (mpg123_getformat(mh, &rate, &chan, &enc) != MPG123_OK) { 36 | log_warnx("mpg123_getformat failed"); 37 | return 0; 38 | } 39 | 40 | if (player_setup(mpg123_encsize(enc) * 8, rate, chan) == -1) 41 | fatal("player_setup"); 42 | 43 | return 1; 44 | } 45 | 46 | int 47 | play_mp3(int fd, const char **errstr) 48 | { 49 | static char buf[AMUSED_BUFSIZ]; 50 | size_t len; 51 | mpg123_handle *mh; 52 | int64_t seek = -1; 53 | int err, ret = -1; 54 | 55 | if ((mh = mpg123_new(NULL, NULL)) == NULL) 56 | fatal("mpg123_new"); 57 | 58 | if (mpg123_open_fd(mh, fd) != MPG123_OK) { 59 | *errstr = "mpg123_open_fd failed"; 60 | close(fd); 61 | return -1; 62 | } 63 | 64 | if (!setup(mh)) 65 | goto done; 66 | 67 | if (mpg123_scan(mh) != MPG123_OK) 68 | goto done; 69 | 70 | player_setduration(mpg123_length(mh)); 71 | 72 | for (;;) { 73 | if (seek != -1) { 74 | seek = mpg123_seek(mh, seek, SEEK_SET); 75 | if (seek < 0) { 76 | ret = 0; 77 | break; 78 | } 79 | player_setpos(seek); 80 | } 81 | 82 | err = mpg123_read(mh, buf, sizeof(buf), &len); 83 | switch (err) { 84 | case MPG123_DONE: 85 | ret = 0; 86 | goto done; 87 | case MPG123_NEW_FORMAT: 88 | if (!setup(mh)) 89 | goto done; 90 | break; 91 | case MPG123_OK: 92 | if (!play(buf, len, &seek)) { 93 | ret = 1; 94 | goto done; 95 | } 96 | break; 97 | default: 98 | log_warnx("skipping mp3 decoding error"); 99 | break; 100 | } 101 | } 102 | 103 | done: 104 | mpg123_delete(mh); 105 | close(fd); 106 | return ret; 107 | } 108 | -------------------------------------------------------------------------------- /player_flac.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "log.h" 28 | #include "player.h" 29 | 30 | struct write_args { 31 | FLAC__StreamDecoder *decoder; 32 | int seek_failed; 33 | }; 34 | 35 | static int 36 | sample_seek(struct write_args *wa, int64_t seek) 37 | { 38 | int ok; 39 | 40 | ok = FLAC__stream_decoder_seek_absolute(wa->decoder, seek); 41 | if (ok) 42 | player_setpos(seek); 43 | else 44 | wa->seek_failed = 1; 45 | return ok; 46 | } 47 | 48 | static FLAC__StreamDecoderWriteStatus 49 | writecb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, 50 | const int32_t * const *src, void *data) 51 | { 52 | struct write_args *wa = data; 53 | static uint8_t buf[AMUSED_BUFSIZ]; 54 | int64_t seek; 55 | int c, i, bps, chans; 56 | size_t len; 57 | 58 | bps = frame->header.bits_per_sample; 59 | chans = frame->header.channels; 60 | 61 | for (i = 0, len = 0; i < frame->header.blocksize; ++i) { 62 | if (len + 4*chans >= sizeof(buf)) { 63 | if (!play(buf, len, &seek)) 64 | goto quit; 65 | if (seek != -1) { 66 | if (sample_seek(wa, seek)) 67 | break; 68 | else 69 | goto quit; 70 | } 71 | len = 0; 72 | } 73 | 74 | for (c = 0; c < chans; ++c) { 75 | switch (bps) { 76 | case 8: 77 | buf[len++] = src[c][i] & 0xff; 78 | break; 79 | case 16: 80 | buf[len++] = src[c][i] & 0xff; 81 | buf[len++] = (src[c][i] >> 8) & 0xff; 82 | break; 83 | case 24: 84 | case 32: 85 | buf[len++] = src[c][i] & 0xff; 86 | buf[len++] = (src[c][i] >> 8) & 0xff; 87 | buf[len++] = (src[c][i] >> 16) & 0xff; 88 | buf[len++] = (src[c][i] >> 24) & 0xff; 89 | break; 90 | default: 91 | log_warnx("unsupported flac bps=%d", bps); 92 | goto quit; 93 | } 94 | } 95 | } 96 | 97 | if (len != 0 && !play(buf, len, &seek)) 98 | goto quit; 99 | 100 | if (seek != -1 && !sample_seek(wa, seek)) 101 | goto quit; 102 | 103 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; 104 | quit: 105 | return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; 106 | } 107 | 108 | static void 109 | metacb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *meta, 110 | void *d) 111 | { 112 | uint32_t sample_rate; 113 | int channels, bits; 114 | 115 | if (meta->type == FLAC__METADATA_TYPE_STREAMINFO) { 116 | bits = meta->data.stream_info.bits_per_sample; 117 | sample_rate = meta->data.stream_info.sample_rate; 118 | channels = meta->data.stream_info.channels; 119 | 120 | if (player_setup(bits, sample_rate, channels) == -1) 121 | fatal("player_setup"); 122 | 123 | player_setduration(meta->data.stream_info.total_samples); 124 | } 125 | } 126 | 127 | static void 128 | errcb(const FLAC__StreamDecoder *decoder, 129 | FLAC__StreamDecoderErrorStatus status, void *data) 130 | { 131 | log_warnx("flac error: %s", 132 | FLAC__StreamDecoderErrorStatusString[status]); 133 | } 134 | 135 | int 136 | play_flac(int fd, const char **errstr) 137 | { 138 | FILE *f; 139 | struct write_args wa; 140 | int s, ok = 1; 141 | FLAC__StreamDecoder *decoder = NULL; 142 | FLAC__StreamDecoderInitStatus init_status; 143 | 144 | if ((f = fdopen(fd, "r")) == NULL) { 145 | *errstr = "fdopen failed"; 146 | close(fd); 147 | return -1; 148 | } 149 | 150 | decoder = FLAC__stream_decoder_new(); 151 | if (decoder == NULL) { 152 | *errstr = "FLAC__stream_decoder_new() failed"; 153 | fclose(f); 154 | return -1; 155 | } 156 | 157 | FLAC__stream_decoder_set_md5_checking(decoder, 1); 158 | 159 | memset(&wa, 0, sizeof(wa)); 160 | wa.decoder = decoder; 161 | 162 | init_status = FLAC__stream_decoder_init_FILE(decoder, f, writecb, 163 | metacb, errcb, &wa); 164 | if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) 165 | fatalx("flac decoder: %s", 166 | FLAC__StreamDecoderInitStatusString[init_status]); 167 | 168 | ok = FLAC__stream_decoder_process_until_end_of_stream(decoder); 169 | 170 | s = FLAC__stream_decoder_get_state(decoder); 171 | FLAC__stream_decoder_delete(decoder); 172 | fclose(f); 173 | 174 | if (s == FLAC__STREAM_DECODER_ABORTED && !wa.seek_failed) 175 | return 1; 176 | else if (!ok && !wa.seek_failed) { 177 | *errstr = "flac decoding error"; 178 | return -1; 179 | } else 180 | return 0; 181 | } 182 | -------------------------------------------------------------------------------- /player_oggvorbis.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #include "log.h" 29 | #include "player.h" 30 | 31 | #ifndef nitems 32 | #define nitems(x) (sizeof(x)/sizeof(x[0])) 33 | #endif 34 | 35 | int 36 | play_oggvorbis(int fd, const char **errstr) 37 | { 38 | static char pcmout[AMUSED_BUFSIZ]; 39 | FILE *f; 40 | OggVorbis_File vf; 41 | vorbis_info *vi; 42 | int64_t seek = -1; 43 | int current_section, ret = 0; 44 | 45 | if ((f = fdopen(fd, "r")) == NULL) { 46 | *errstr = "fdopen failed"; 47 | close(fd); 48 | return -1; 49 | } 50 | 51 | if (ov_open_callbacks(f, &vf, NULL, 0, OV_CALLBACKS_NOCLOSE) < 0) { 52 | *errstr = "input is not an Ogg bitstream"; 53 | fclose(f); 54 | return -1; 55 | } 56 | 57 | /* 58 | * we could extract some tags by looping over the NULL 59 | * terminated array returned by ov_comment(&vf, -1), see 60 | * previous revision of this file. 61 | */ 62 | vi = ov_info(&vf, -1); 63 | if (player_setup(16, vi->rate, vi->channels) == -1) 64 | fatal("player_setup"); 65 | 66 | player_setduration(ov_time_total(&vf, -1) * vi->rate); 67 | 68 | for (;;) { 69 | long r; 70 | 71 | if (seek != -1) { 72 | r = ov_pcm_seek(&vf, seek); 73 | if (r != 0) 74 | break; 75 | player_setpos(seek); 76 | } 77 | 78 | r = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, 79 | ¤t_section); 80 | if (r == 0) 81 | break; 82 | else if (r > 0) { 83 | /* TODO: deal with sample rate changes */ 84 | if (!play(pcmout, r, &seek)) { 85 | ret = 1; 86 | break; 87 | } 88 | } 89 | } 90 | 91 | ov_clear(&vf); 92 | fclose(f); 93 | return ret; 94 | } 95 | -------------------------------------------------------------------------------- /player_opus.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "log.h" 28 | #include "player.h" 29 | 30 | #ifndef nitems 31 | #define nitems(x) (sizeof(x)/sizeof(x[0])) 32 | #endif 33 | 34 | int 35 | play_opus(int fd, const char **errstr) 36 | { 37 | static int16_t pcm[AMUSED_BUFSIZ / 2]; 38 | static uint8_t out[AMUSED_BUFSIZ]; 39 | OggOpusFile *of; 40 | void *f; 41 | int64_t seek = -1; 42 | int r, ret = 0; 43 | OpusFileCallbacks cb = {NULL, NULL, NULL, NULL}; 44 | int i, li, prev_li = -1, duration_set = 0; 45 | 46 | if ((f = op_fdopen(&cb, fd, "r")) == NULL) { 47 | *errstr = "fdopen failed"; 48 | close(fd); 49 | return -1; 50 | } 51 | 52 | of = op_open_callbacks(f, &cb, NULL, 0, &r); 53 | if (of == NULL) { 54 | fclose(f); 55 | return -1; 56 | } 57 | 58 | for (;;) { 59 | if (seek != -1) { 60 | r = op_pcm_seek(of, seek); 61 | if (r != 0) 62 | break; 63 | player_setpos(seek); 64 | } 65 | 66 | /* NB: will downmix multichannels files into two channels */ 67 | r = op_read_stereo(of, pcm, nitems(pcm)); 68 | if (r == OP_HOLE) /* corrupt file segment? */ 69 | continue; 70 | if (r < 0) { 71 | *errstr = "opus decoding error"; 72 | ret = -1; 73 | break; 74 | } 75 | if (r == 0) 76 | break; /* eof */ 77 | 78 | li = op_current_link(of); 79 | if (li != prev_li) { 80 | const OpusHead *head; 81 | 82 | prev_li = li; 83 | head = op_head(of, li); 84 | if (head->input_sample_rate && 85 | player_setup(16, head->input_sample_rate, 2) == -1) 86 | fatal("player_setup"); 87 | 88 | if (!duration_set) { 89 | duration_set = 1; 90 | player_setduration(op_pcm_total(of, -1)); 91 | } 92 | } 93 | 94 | for (i = 0; i < 2*r; ++i) { 95 | out[2*i+0] = pcm[i] & 0xFF; 96 | out[2*i+1] = (pcm[i] >> 8) & 0xFF; 97 | } 98 | 99 | if (!play(out, 4*r, &seek)) { 100 | ret = 1; 101 | break; 102 | } 103 | } 104 | 105 | op_free(of); 106 | return ret; 107 | } 108 | -------------------------------------------------------------------------------- /playlist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "log.h" 24 | #include "xmalloc.h" 25 | #include "playlist.h" 26 | 27 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 28 | 29 | struct playlist playlist; 30 | enum play_state play_state; 31 | int repeat_one; 32 | int repeat_all = 1; 33 | int consume; 34 | ssize_t play_off = -1; 35 | const char *current_song; 36 | int64_t current_position; 37 | int64_t current_duration; 38 | 39 | static void 40 | setsong(ssize_t i) 41 | { 42 | free((char *)current_song); 43 | if (i == -1) 44 | current_song = NULL; 45 | else 46 | current_song = xstrdup(playlist.songs[i]); 47 | } 48 | 49 | void 50 | playlist_swap(struct playlist *p, ssize_t off) 51 | { 52 | ssize_t i = -1; 53 | 54 | if (off > p->len) 55 | off = -1; 56 | 57 | if (current_song != NULL && off < 0) { 58 | /* try to match the currently played song */ 59 | for (i = 0; i < p->len; ++i) { 60 | if (!strcmp(current_song, p->songs[i])) 61 | break; 62 | } 63 | if (i == p->len) 64 | i = -1; 65 | } 66 | 67 | playlist_truncate(); 68 | 69 | if (i != -1) 70 | play_off = i; 71 | else if (off >= 0) 72 | play_off = off; 73 | 74 | playlist.len = p->len; 75 | playlist.cap = p->cap; 76 | playlist.songs = p->songs; 77 | 78 | if (play_state == STATE_STOPPED) 79 | setsong(play_off); 80 | } 81 | 82 | void 83 | playlist_push(struct playlist *playlist, const char *path) 84 | { 85 | size_t newcap; 86 | 87 | if (playlist->len == playlist->cap) { 88 | newcap = MAX(16, playlist->cap * 1.5); 89 | playlist->songs = xrecallocarray(playlist->songs, 90 | playlist->cap, newcap, sizeof(*playlist->songs)); 91 | playlist->cap = newcap; 92 | } 93 | 94 | playlist->songs[playlist->len++] = xstrdup(path); 95 | } 96 | 97 | void 98 | playlist_enqueue(const char *path) 99 | { 100 | playlist_push(&playlist, path); 101 | } 102 | 103 | const char * 104 | playlist_advance(void) 105 | { 106 | if (playlist.len == 0) { 107 | play_state = STATE_STOPPED; 108 | return NULL; 109 | } 110 | 111 | play_off++; 112 | if (play_off == playlist.len) { 113 | if (repeat_all) 114 | play_off = 0; 115 | else { 116 | play_state = STATE_STOPPED; 117 | play_off = -1; 118 | setsong(play_off); 119 | return NULL; 120 | } 121 | } 122 | 123 | setsong(play_off); 124 | play_state = STATE_PLAYING; 125 | return playlist.songs[play_off]; 126 | } 127 | 128 | const char * 129 | playlist_previous(void) 130 | { 131 | if (playlist.len == 0) { 132 | play_state = STATE_STOPPED; 133 | return NULL; 134 | } 135 | 136 | play_off--; 137 | if (play_off < 0) { 138 | if (repeat_all) 139 | play_off = playlist.len - 1; 140 | else { 141 | play_state = STATE_STOPPED; 142 | play_off = -1; 143 | setsong(play_off); 144 | return NULL; 145 | } 146 | } 147 | 148 | setsong(play_off); 149 | play_state = STATE_PLAYING; 150 | return playlist.songs[play_off]; 151 | } 152 | 153 | void 154 | playlist_reset(void) 155 | { 156 | play_off = -1; 157 | } 158 | 159 | void 160 | playlist_free(struct playlist *playlist) 161 | { 162 | size_t i; 163 | 164 | for (i = 0; i < playlist->len; ++i) 165 | free(playlist->songs[i]); 166 | free(playlist->songs); 167 | playlist->songs = NULL; 168 | 169 | playlist->len = 0; 170 | playlist->cap = 0; 171 | } 172 | 173 | void 174 | playlist_truncate(void) 175 | { 176 | playlist_free(&playlist); 177 | play_off = -1; 178 | } 179 | 180 | void 181 | playlist_dropcurrent(void) 182 | { 183 | size_t i; 184 | 185 | if (play_off == -1 || playlist.len == 0) 186 | return; 187 | 188 | free(playlist.songs[play_off]); 189 | setsong(-1); 190 | 191 | playlist.len--; 192 | for (i = play_off; i < playlist.len; ++i) 193 | playlist.songs[i] = playlist.songs[i+1]; 194 | play_off--; 195 | 196 | playlist.songs[playlist.len] = NULL; 197 | } 198 | 199 | const char * 200 | playlist_jump(const char *arg) 201 | { 202 | size_t i; 203 | 204 | for (i = 0; i < playlist.len; ++i) { 205 | if (strcasestr(playlist.songs[i], arg) == 0) 206 | break; 207 | } 208 | 209 | if (i == playlist.len) 210 | return NULL; 211 | 212 | play_state = STATE_PLAYING; 213 | play_off = i; 214 | setsong(play_off); 215 | return playlist.songs[i]; 216 | } 217 | 218 | static inline void 219 | swap(size_t a, size_t b) 220 | { 221 | char *tmp; 222 | 223 | tmp = playlist.songs[a]; 224 | playlist.songs[a] = playlist.songs[b]; 225 | playlist.songs[b] = tmp; 226 | } 227 | 228 | void 229 | playlist_shuffle(int all) 230 | { 231 | size_t i, j, start = 0; 232 | 233 | if (playlist.len == 0) 234 | return; 235 | 236 | if (play_off >= 0 && !all) 237 | start = play_off; 238 | 239 | if (play_off >= 0) { 240 | swap(play_off, start); 241 | play_off = start; 242 | start++; 243 | } 244 | 245 | for (i = playlist.len - 1; i > start; i--) { 246 | j = start + arc4random_uniform(i - start); 247 | swap(i, j); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /playlist.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Omar Polo 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef PLAYLIST_H 18 | #define PLAYLIST_H 19 | 20 | struct playlist { 21 | size_t len; 22 | size_t cap; 23 | char **songs; 24 | }; 25 | 26 | enum play_state { 27 | STATE_STOPPED, 28 | STATE_PLAYING, 29 | STATE_PAUSED, 30 | }; 31 | 32 | extern struct playlist playlist; 33 | 34 | extern enum play_state play_state; 35 | extern int repeat_one; 36 | extern int repeat_all; 37 | extern int consume; 38 | extern ssize_t play_off; 39 | extern const char *current_song; 40 | extern int64_t current_position; 41 | extern int64_t current_duration; 42 | 43 | void playlist_swap(struct playlist *, ssize_t); 44 | void playlist_push(struct playlist *, const char *); 45 | void playlist_enqueue(const char *); 46 | const char *playlist_advance(void); 47 | const char *playlist_previous(void); 48 | void playlist_reset(void); 49 | void playlist_free(struct playlist *); 50 | void playlist_truncate(void); 51 | void playlist_dropcurrent(void); 52 | const char *playlist_jump(const char *); 53 | void playlist_shuffle(int); 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /songmeta/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | PROG = songmeta 4 | SRCS = songmeta.c flac.c id3v1.c id3v2.c ogg.c opus.c text.c \ 5 | vorbis.c ../log.c 6 | 7 | OBJS = ${SRCS:.c=.o} ${COBJS:%=../compat/%} 8 | 9 | DISTFILES = Makefile flac.c id3v1.c id3v2.c ogg.c ogg.h opus.c \ 10 | songmeta.1 songmeta.c songmeta.h text.c vorbis.c 11 | 12 | TOPDIR = .. 13 | 14 | all: ${PROG} 15 | 16 | ../config.mk ../config.h: ../configure ../tests.c 17 | @echo "$@ is out of date; please run ../configure" 18 | @exit 1 19 | 20 | include ../config.mk 21 | 22 | # --- targets --- 23 | 24 | ${PROG}: ${OBJS} 25 | ${CC} -o $@ ${OBJS} ${LDFLAGS} ${LDADD} 26 | 27 | clean: 28 | rm -f ${OBJS} ${OBJS:.o=.d} ${PROG} 29 | 30 | distclean: clean 31 | 32 | install: 33 | mkdir -p ${DESTDIR}${BINDIR} 34 | mkdir -p ${DESTDIR}${MANDIR}/man1 35 | ${INSTALL_PROGRAM} ${PROG} ${DESTDIR}${BINDIR} 36 | ${INSTALL_MAN} songmeta.1 ${DESTDIR}${MANDIR}/man1/${PROG}.1 37 | 38 | install-local: 39 | mkdir -p ${HOME}/bin 40 | ${INSTALL_PROGRAM} ${PROG} ${HOME}/bin 41 | 42 | uninstall: 43 | rm ${DESTDIR}${BINDIR}/${PROG} 44 | rm ${DESTDIR}${MANDIR}/man1/${PROG}.1 45 | 46 | .c.o: 47 | ${CC} -I.. -I../compat ${CFLAGS} -c $< -o $@ 48 | 49 | # --- maintainer targets --- 50 | 51 | dist: 52 | mkdir -p ${DESTDIR}/ 53 | ${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/ 54 | 55 | # --- dependency management --- 56 | 57 | # these .d files are produced during the first build if the compiler 58 | # supports it. 59 | 60 | -include flac.d 61 | -include id3v1.d 62 | -include id3v2.d 63 | -include ogg.d 64 | -include opus.d 65 | -include songmeta.d 66 | -include text.d 67 | -include vorbis.d 68 | -include ../log.d 69 | -------------------------------------------------------------------------------- /songmeta/flac.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | /* 27 | * FLAC metadata handling. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "log.h" 36 | #include "songmeta.h" 37 | 38 | int 39 | flac_dump(FILE *fp, const char *name, const char *filter) 40 | { 41 | uint8_t btype; 42 | uint8_t magic[4], len[4]; 43 | size_t r, i, n, blen; 44 | int last = 0; 45 | 46 | if ((r = fread(magic, 1, sizeof(magic), fp)) != sizeof(magic)) 47 | return (-1); 48 | 49 | if (memcmp(magic, "fLaC", 4) != 0) { 50 | log_warnx("not a flac file %s", name); 51 | return (-1); 52 | } 53 | 54 | while (!last) { 55 | if (fread(&btype, 1, 1, fp) != 1) 56 | return (-1); 57 | last = btype & 0x80; 58 | 59 | if (fread(len, 1, 3, fp) != 3) 60 | return (-1); 61 | 62 | blen = (len[0] << 16)|(len[1] << 8)|len[2]; 63 | 64 | if ((btype & 0x07) != 0x04) { 65 | //printf("skipping %zu bytes of block type %d\n", 66 | // blen, (btype & 0x07)); 67 | /* not a vorbis comment, skip... */ 68 | if (fseeko(fp, blen, SEEK_CUR) == -1) 69 | return (-1); 70 | continue; 71 | } 72 | 73 | if (fread(len, 1, 4, fp) != 4) 74 | return (-1); 75 | 76 | /* The vorbis comment has little-endian integers */ 77 | 78 | blen = (len[3] << 24)|(len[2] << 16)|(len[1] << 8)|len[0]; 79 | /* skip the vendor string comment */ 80 | if (fseeko(fp, blen, SEEK_CUR) == -1) 81 | return (-1); 82 | 83 | if (fread(len, 1, 4, fp) != 4) 84 | return (-1); 85 | n = (len[3] << 24)|(len[2] << 16)|(len[1] << 8)|len[0]; 86 | 87 | for (i = 0; i < n; ++i) { 88 | char *m, *v; 89 | 90 | if (fread(len, 1, 4, fp) != 4) 91 | return (-1); 92 | blen = (len[3] << 24)|(len[2] << 16)|(len[1] << 8)|len[0]; 93 | 94 | if ((m = malloc(blen + 1)) == NULL) 95 | fatal("malloc"); 96 | 97 | if (fread(m, 1, blen, fp) != blen) { 98 | free(m); 99 | return (-1); 100 | } 101 | m[blen] = '\0'; 102 | 103 | if ((v = strchr(m, '=')) == NULL) { 104 | log_warnx("missing field name!"); 105 | free(m); 106 | return (-1); 107 | } 108 | 109 | *v++ = '\0'; 110 | printf("%s = %s\n", m, v); 111 | 112 | free(m); 113 | } 114 | } 115 | 116 | return (0); 117 | } 118 | -------------------------------------------------------------------------------- /songmeta/id3v1.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | /* 27 | * ID3v1 and 1.1 handling. 28 | */ 29 | 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "log.h" 38 | #include "songmeta.h" 39 | 40 | #define ID3v1_SIZE 128 41 | 42 | int 43 | id3v1_dump(int fd, const char *name, const char *filter) 44 | { 45 | struct stat sb; 46 | char *s, *e, id3[ID3v1_SIZE]; 47 | char buf[5]; /* wide enough for YYYY + NUL */ 48 | ssize_t r; 49 | off_t off; 50 | 51 | if (fstat(fd, &sb) == -1) { 52 | log_warn("fstat %s", name); 53 | return (-1); 54 | } 55 | 56 | if (sb.st_size < ID3v1_SIZE) { 57 | log_warnx("no id3 section found in %s", name); 58 | return (-1); 59 | } 60 | off = sb.st_size - ID3v1_SIZE; 61 | r = pread(fd, id3, ID3v1_SIZE, off); 62 | if (r == -1 || r != ID3v1_SIZE) { 63 | log_warn("failed to read id3 section in %s", name); 64 | return (-1); 65 | } 66 | 67 | s = id3; 68 | if (strncmp(s, "TAG", 3) != 0) 69 | goto bad; 70 | s += 3; 71 | 72 | if (memchr(s, '\0', 30) == NULL) 73 | goto bad; 74 | if (*s) 75 | printfield("title", filter, "Title", 1, s); 76 | else if (filter != NULL && matchfield("title", filter)) 77 | return (-1); 78 | s += 30; 79 | 80 | if (memchr(s, '\0', 30) == NULL) 81 | goto bad; 82 | if (*s) 83 | printfield("artist", filter, "Artist", 1, s); 84 | else if (filter != NULL && matchfield("artist", filter)) 85 | return (-1); 86 | s += 30; 87 | 88 | if (memchr(s, '\0', 30) == NULL) 89 | goto bad; 90 | if (*s) 91 | printfield("album", filter, "Album", 1, s); 92 | else if (filter != NULL && matchfield("album", filter)) 93 | return (-1); 94 | s += 30; 95 | 96 | if (!isdigit((unsigned char)s[0]) || 97 | !isdigit((unsigned char)s[1]) || 98 | !isdigit((unsigned char)s[2]) || 99 | !isdigit((unsigned char)s[3])) 100 | goto bad; 101 | memcpy(buf, s, 4); 102 | buf[4] = '\0'; 103 | printfield("year", filter, "Year", 0, buf); 104 | s += 4; 105 | 106 | if ((e = memchr(s, '\0', 30)) == NULL) 107 | goto bad; 108 | s += strspn(s, " \t"); 109 | if (*s) 110 | printfield("comment", filter, "Comment", 1, s); 111 | else if (filter != NULL && matchfield("comment", filter)) 112 | return (-1); 113 | 114 | /* ID3v1.1: track number is inside the comment space */ 115 | 116 | if (s[28] == '\0' && s[29] != '\0') { 117 | snprintf(buf, sizeof(buf), "%d", (unsigned int)s[29]); 118 | printfield("track", filter, "Track #", 0, s); 119 | } else if (filter != NULL && matchfield("track", filter)) 120 | return (-1); 121 | 122 | return (0); 123 | 124 | bad: 125 | log_warnx("bad id3 section in %s", name); 126 | return (-1); 127 | } 128 | -------------------------------------------------------------------------------- /songmeta/id3v2.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | /* 27 | * ID3v2(.4.0) handling. 28 | */ 29 | 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "log.h" 41 | #include "songmeta.h" 42 | 43 | #ifndef nitems 44 | #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) 45 | #endif 46 | 47 | #define ID3v2_HDR_SIZE 10 48 | #define ID3v2_FRAME_SIZE 10 49 | 50 | #define F_UNSYNC 0x80 51 | #define F_EXTHDR 0x40 52 | #define F_EXPIND 0x20 53 | #define F_FOOTER 0x10 54 | 55 | static const struct fnmap { 56 | const char *id; 57 | const char *name; 58 | const char *pretty; 59 | } map[] = { 60 | /* AENC Audio encryption */ 61 | /* APIC Attached picture */ 62 | /* ASPI Audio seek point index */ 63 | 64 | { "COMM", "comment", "Comment" }, 65 | /* COMR Commercial frame */ 66 | 67 | /* ENCR Encryption method registration */ 68 | /* EQU2 Equalisation (2) */ 69 | /* ETCO Event time codes */ 70 | 71 | /* GEOB General encapsulated object */ 72 | /* GRID Group identification registration */ 73 | 74 | /* LINK Linked information */ 75 | 76 | /* MCDI Music CD identifier */ 77 | /* MLLT MPEG location lookup table */ 78 | 79 | /* OWNE Ownership frame */ 80 | 81 | /* PRIV Private frame */ 82 | /* PCNT Play counter */ 83 | /* POPM Popularimeter */ 84 | /* POSS Position synchronisation frame */ 85 | 86 | /* RBUF Recommended buffer size */ 87 | /* RVA2 Relative volume adjustment (2) */ 88 | /* RVRB Reverb */ 89 | 90 | /* SEEK Seek frame */ 91 | /* SIGN Signature frame */ 92 | /* SYLT Synchronised lyric text */ 93 | /* SYTC Synchronised tempo codes */ 94 | 95 | { "TALB", "album", "Album" }, 96 | { "TBPM", "bpm", "beats per minute" }, 97 | { "TCOM", "composer", "Composer" }, 98 | { "TCON", "content-type", "Content type" }, 99 | { "TCOP", "copyright-message", "Copyright message" }, 100 | { "TDEN", "encoding-time", "Encoding time" }, 101 | { "TDLY", "playlist-delay", "Playlist delay" }, 102 | { "TDOR", "original-release-time","Original release time" }, 103 | { "TDRC", "recording-time", "Recording time" }, 104 | { "TDRL", "release-time", "Release time" }, 105 | { "TDTG", "tagging-time", "Tagging time" }, 106 | { "TENC", "encoded-by", "Encoded by" }, 107 | { "TEXT", "lyricist", "Lyricist/Text writer" }, 108 | { "TFLT", "file-type", "File type" }, 109 | { "TIPL", "involved-people", "Involved people list" }, 110 | { "TIT1", "content-group-description", "Content group description" }, 111 | { "TIT2", "title", "Title" }, 112 | { "TIT3", "subtitle", "Subtitle" }, 113 | { "TKEY", "initial-key", "Initial key" }, 114 | { "TLAN", "language", "Language" }, 115 | { "TLEN", "length", "Length" }, 116 | { "TMCL", "musician", "Musician credits list" }, 117 | { "TMED", "media-type", "Media type" }, 118 | { "TMOO", "mood", "Mood" }, 119 | { "TOAL", "original-title", "Original album/movie/show title" }, 120 | { "TOFN", "original-filename", "Original filename" }, 121 | { "TOLY", "original-lyricist", "Original lyricist(s)/text writer(s)" }, 122 | { "TOPE", "original-artist", "Original artist(s)/performer(s)" }, 123 | { "TOWN", "licensee", "File owner/licensee" }, 124 | { "TPE1", "lead-performer", "Lead performer(s)/Soloist(s)" }, 125 | { "TPE2", "band", "Band/orchestra/accompaniment" }, 126 | { "TPE3", "conductor", "Conductor/performer refinement" }, 127 | { "TPE4", "interpreted-by", "Interpreted, remixed, or otherwise modified by" }, 128 | { "TPOS", "part", "Part of a set" }, 129 | { "TPRO", "notice", "Produced notice" }, 130 | { "TPUB", "publisher", "Publisher" }, 131 | { "TRCK", "track", "Track number/Position in set" }, 132 | { "TRSN", "radio-name", "Internet radio station name" }, 133 | { "TRSO", "radio-owner", "Internet radio station owner" }, 134 | { "TSOA", "album-order", "Album sort order" }, 135 | { "TSOP", "performer-order", "Performer sort order" }, 136 | { "TSOT", "title-order", "Title sort order" }, 137 | { "TSRC", "isrc", "ISRC (international standard recording code)" }, 138 | { "TSSE", "encoder", "Software/Hardware and settings used for encoding" }, 139 | { "TSST", "subtitle", "Set subtitle" }, 140 | /* TXXX user defined text information frame */ 141 | 142 | /* UFID Unique file identifier */ 143 | /* USER Terms of use */ 144 | /* USLT Unsynchronised lyric/text transcription */ 145 | 146 | /* WCOM Commercial information */ 147 | /* WCOP Copyright/legal information */ 148 | /* WOAF Official audio file webpage */ 149 | /* WOAR Official artist/performer webpage */ 150 | /* WOAS Official audio source webpage */ 151 | /* WORS Official internet radio station homepage */ 152 | /* WPAY Payment */ 153 | /* WPUB Publishers official webpage */ 154 | /* WXXX User defined URL link frame */ 155 | }; 156 | 157 | static int 158 | mapcmp(const void *k, const void *e) 159 | { 160 | const struct fnmap *f = e; 161 | 162 | return (memcmp(k, f->id, 4)); 163 | } 164 | 165 | static uint32_t 166 | fromss32(uint32_t x) 167 | { 168 | uint8_t y[4]; 169 | 170 | memcpy(y, &x, sizeof(x)); 171 | return (y[0] << 21) | (y[1] << 14) | (y[2] << 7) | y[3]; 172 | } 173 | 174 | int 175 | id3v2_dump(int fd, const char *name, const char *filter) 176 | { 177 | struct fnmap *f; 178 | char hdr[ID3v2_HDR_SIZE]; 179 | char *s; 180 | ssize_t r; 181 | uint8_t flags; 182 | uint32_t size, fsize; 183 | 184 | if ((r = read(fd, hdr, sizeof(hdr))) == -1 || 185 | r != ID3v2_HDR_SIZE) { 186 | log_warn("read failed: %s", name); 187 | return (-1); 188 | } 189 | 190 | s = hdr; 191 | if (strncmp(s, "ID3", 3)) 192 | goto bad; 193 | s += 3; 194 | 195 | if (s[0] != 0x04 && s[1] != 0x00) 196 | goto bad; 197 | s += 2; 198 | 199 | flags = s[0]; 200 | if ((s[0] & 0x0F) != 0) 201 | goto bad; 202 | s += 1; 203 | 204 | #ifdef DEBUG 205 | const char *sep = ""; 206 | printf("flags:\t"); 207 | if (flags & F_UNSYNC) printf("%sunsync", sep), sep = ","; 208 | if (flags & F_EXTHDR) printf("%sexthdr", sep), sep = ","; 209 | if (flags & F_EXPIND) printf("%sexpind", sep), sep = ","; 210 | if (flags & F_FOOTER) printf("%sfooter", sep), sep = ","; 211 | printf(" (0x%x)\n", flags); 212 | #endif 213 | 214 | if (flags & F_EXPIND) { 215 | log_warnx("don't know how to handle the extended header yet."); 216 | return (-1); 217 | } 218 | 219 | memcpy(&size, s, 4); 220 | size = fromss32(size); 221 | 222 | while (size > 0) { 223 | if ((r = read(fd, hdr, sizeof(hdr))) == -1 || 224 | r != ID3v2_FRAME_SIZE) { 225 | log_warn("read failed: %s", name); 226 | return (-1); 227 | } 228 | 229 | memcpy(&fsize, hdr + 4, sizeof(fsize)); 230 | fsize = fromss32(fsize); 231 | if (fsize == 0) 232 | break; /* XXX padding?? */ 233 | if (fsize + 10 > size) { 234 | log_warnx("bad frame length (%d vs %d)", fsize, size); 235 | return (-1); 236 | } 237 | size -= fsize + 10; 238 | 239 | f = bsearch(hdr, map, nitems(map), sizeof(map[0]), mapcmp); 240 | if (f == NULL) { 241 | if (lseek(fd, fsize, SEEK_CUR) == -1) { 242 | log_warn("lseek"); 243 | return (-1); 244 | } 245 | continue; 246 | } 247 | 248 | /* XXX skip encoding for now */ 249 | lseek(fd, SEEK_CUR, 1); 250 | fsize--; /* XXX */ 251 | 252 | if (readprintfield(f->name, filter, f->pretty, 253 | ENC_UTF8, fd, fsize) == -1) 254 | return (-1); 255 | } 256 | 257 | return (0); 258 | 259 | bad: 260 | log_warnx("bad ID3v2 section"); 261 | return (-1); 262 | } 263 | -------------------------------------------------------------------------------- /songmeta/ogg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | /* 27 | * Ogg file-format handling. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "log.h" 36 | #include "ogg.h" 37 | 38 | struct ogg { 39 | FILE *fp; 40 | const char *name; 41 | int firstpkt; 42 | uint32_t sn; 43 | int chained; /* use only the sn stream */ 44 | size_t plen; 45 | }; 46 | 47 | static int 48 | readpkt(struct ogg *ogg) 49 | { 50 | uint32_t sn; 51 | uint8_t magic[4], ssv, htype, ps, b; 52 | size_t r; 53 | int i; 54 | 55 | again: 56 | ogg->plen = 0; 57 | if ((r = fread(magic, 1, sizeof(magic), ogg->fp)) != sizeof(magic)) 58 | return (-1); 59 | 60 | if (memcmp(magic, "OggS", 4) != 0) { 61 | log_warnx("not an ogg file: %s", ogg->name); 62 | return (-1); 63 | } 64 | 65 | if (fread(&ssv, 1, 1, ogg->fp) != 1 || 66 | fread(&htype, 1, 1, ogg->fp) != 1) 67 | return (-1); 68 | 69 | /* skip the absolute granule position */ 70 | if (fseeko(ogg->fp, 8, SEEK_CUR) == -1) 71 | return (-1); 72 | 73 | /* the serial number of the stream */ 74 | if (fread(&sn, 1, sizeof(sn), ogg->fp) != sizeof(ogg->sn)) 75 | return (-1); 76 | sn = le32toh(sn); 77 | 78 | /* ignore sequence number and crc32 for now */ 79 | if (fseeko(ogg->fp, 4 + 4, SEEK_CUR) == -1) 80 | return (-1); 81 | 82 | if (fread(&ps, 1, 1, ogg->fp) != 1) 83 | return (-1); 84 | 85 | ogg->plen = 0; 86 | for (i = 0; i < ps; ++i) { 87 | if (fread(&b, 1, 1, ogg->fp) != 1) 88 | return (-1); 89 | ogg->plen += b; 90 | } 91 | 92 | /* found some data without a suitable stream */ 93 | if (!ogg->chained && !(htype & 0x02)) 94 | return (-1); 95 | 96 | if (ogg->chained && ogg->sn != sn) { 97 | /* not "our" stream */ 98 | if (fseeko(ogg->fp, ogg->plen, SEEK_CUR) == -1) 99 | return (-1); 100 | ogg->plen = 0; 101 | goto again; 102 | } 103 | 104 | ogg->sn = sn; 105 | return (0); 106 | } 107 | 108 | struct ogg * 109 | ogg_open(FILE *fp, const char *name) 110 | { 111 | struct ogg *ogg; 112 | 113 | ogg = calloc(1, sizeof(*ogg)); 114 | if (ogg == NULL) 115 | return (NULL); 116 | 117 | ogg->fp = fp; 118 | ogg->name = name; 119 | ogg->firstpkt = 1; 120 | 121 | if (readpkt(ogg) == -1) { 122 | free(ogg); 123 | return (NULL); 124 | } 125 | 126 | return (ogg); 127 | } 128 | 129 | size_t 130 | ogg_read(struct ogg *ogg, void *buf, size_t len) 131 | { 132 | size_t r; 133 | 134 | if (len == 0) 135 | return (0); 136 | 137 | if (ogg->plen == 0 && readpkt(ogg) == -1) 138 | return (0); 139 | 140 | if (len > ogg->plen) 141 | len = ogg->plen; 142 | 143 | r = fread(buf, 1, len, ogg->fp); 144 | ogg->plen -= r; 145 | return (r); 146 | } 147 | 148 | int 149 | ogg_seek(struct ogg *ogg, off_t n) 150 | { 151 | /* not implemented */ 152 | if (n < 0) 153 | return (-1); 154 | 155 | while (n > 0) { 156 | if (ogg->plen == 0 && readpkt(ogg) == -1) 157 | return (-1); 158 | 159 | if (n >= ogg->plen) { 160 | if (fseeko(ogg->fp, ogg->plen, SEEK_CUR) == -1) 161 | return (-1); 162 | n -= ogg->plen; 163 | ogg->plen = 0; 164 | continue; 165 | } 166 | 167 | if (fseeko(ogg->fp, n, SEEK_CUR) == -1) 168 | return (-1); 169 | ogg->plen -= n; 170 | break; 171 | } 172 | 173 | return (0); 174 | } 175 | 176 | int 177 | ogg_skip_page(struct ogg *ogg) 178 | { 179 | return (ogg_seek(ogg, ogg->plen)); 180 | } 181 | 182 | void 183 | ogg_use_current_stream(struct ogg *ogg) 184 | { 185 | ogg->chained = 1; 186 | } 187 | 188 | int 189 | ogg_rewind(struct ogg *ogg) 190 | { 191 | if (fseeko(ogg->fp, 0, SEEK_SET) == -1) 192 | return (-1); 193 | 194 | ogg->plen = 0; 195 | ogg->firstpkt = 1; 196 | ogg->chained = 0; 197 | return (0); 198 | } 199 | 200 | void 201 | ogg_close(struct ogg *ogg) 202 | { 203 | /* it's up to the caller to close ogg->fp */ 204 | free(ogg); 205 | } 206 | -------------------------------------------------------------------------------- /songmeta/ogg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | /* 27 | * Ogg file-format handling. 28 | */ 29 | 30 | struct ogg; 31 | 32 | struct ogg *ogg_open(FILE *, const char *); 33 | size_t ogg_read(struct ogg *, void *, size_t); 34 | int ogg_seek(struct ogg *, off_t); 35 | int ogg_skip_page(struct ogg *); 36 | void ogg_use_current_stream(struct ogg *); 37 | int ogg_rewind(struct ogg *); 38 | void ogg_close(struct ogg *); 39 | -------------------------------------------------------------------------------- /songmeta/opus.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "ogg.h" 32 | #include "log.h" 33 | #include "songmeta.h" 34 | 35 | int 36 | opus_match(struct ogg *ogg) 37 | { 38 | uint8_t hdr[8], v; 39 | 40 | if (ogg_read(ogg, hdr, sizeof(hdr)) != sizeof(hdr)) 41 | return (-1); 42 | if (memcmp(hdr, "OpusHead", 8) != 0) 43 | return (-1); 44 | 45 | ogg_use_current_stream(ogg); 46 | 47 | if (ogg_read(ogg, &v, 1) != 1) 48 | return (-1); 49 | if (v < 1 || v > 2) { 50 | log_warnx("unsupported opus version %d", v); 51 | return (-1); 52 | } 53 | 54 | /* skip the rest of the identification header */ 55 | if (ogg_skip_page(ogg) == -1) 56 | return (-1); 57 | 58 | /* now there should be the optional tag section */ 59 | if (ogg_read(ogg, hdr, sizeof(hdr)) != sizeof(hdr)) 60 | return (-1); 61 | if (memcmp(hdr, "OpusTags", 8) != 0) 62 | return (-1); 63 | 64 | return (0); 65 | } 66 | 67 | int 68 | opus_dump(struct ogg *ogg, const char *name, const char *filter) 69 | { 70 | static char buf[2048]; /* should be enough... */ 71 | char *v; 72 | uint32_t i, n, l, len; 73 | 74 | if (ogg_read(ogg, &len, sizeof(len)) != sizeof(len)) 75 | return (-1); 76 | len = le32toh(len); 77 | if (ogg_seek(ogg, +len) == -1) 78 | return (-1); 79 | 80 | if (ogg_read(ogg, &n, sizeof(n)) != sizeof(n)) 81 | return (-1); 82 | n = le32toh(n); 83 | 84 | for (i = 0; i < n; ++i) { 85 | if (ogg_read(ogg, &len, sizeof(len)) != sizeof(len)) 86 | return (-1); 87 | len = le32toh(len); 88 | 89 | l = len; 90 | if (l >= sizeof(buf)) 91 | l = len - 1; 92 | len -= l; 93 | 94 | if (ogg_read(ogg, buf, l) != l || 95 | ogg_seek(ogg, +len) == -1) 96 | return (-1); 97 | buf[l] = '\0'; 98 | 99 | if ((v = strchr(buf, '=')) == NULL) 100 | return (-1); 101 | *v++ = '\0'; 102 | 103 | /* 104 | * XXX should probably ignore R128_TRACK_GAIN and 105 | * R128_ALBUM_GAIN. 106 | */ 107 | 108 | printf("%s = %s\n", buf, v); 109 | } 110 | 111 | 112 | return (-1); 113 | } 114 | -------------------------------------------------------------------------------- /songmeta/songmeta.1: -------------------------------------------------------------------------------- 1 | .\" This is free and unencumbered software released into the public domain. 2 | .\" 3 | .\" Anyone is free to copy, modify, publish, use, compile, sell, or 4 | .\" distribute this software, either in source code form or as a compiled 5 | .\" binary, for any purpose, commercial or non-commercial, and by any 6 | .\" means. 7 | .\" 8 | .Dd May 26, 2024 9 | .Dt SONGMETA 1 10 | .Os 11 | .Sh NAME 12 | .Nm songmeta 13 | .Nd song metadata extractor 14 | .Sh SYNOPSIS 15 | .Nm 16 | .Op Fl r 17 | .\" not there yet 18 | .\" .Op Fl g Ar field 19 | .Ar 20 | .Sh DESCRIPTION 21 | .Nm 22 | extracts and prints to standard output the metadata for the music files 23 | given as argument. 24 | .Pp 25 | .Nm 26 | supports Ogg Opus, Ogg Vorbis, Flac, ID3v1 and ID3v2 metadata. 27 | Not all combinations may work. 28 | .Pp 29 | The following options are available: 30 | .Bl -tag -width Ds 31 | .It Fl r 32 | Show non-printable character as-is instead of replacing them with 33 | .Sq \&? . 34 | This option might mess up the terminal. 35 | .El 36 | .Sh AUTHORS 37 | .An -nosplit 38 | The 39 | .Nm 40 | program was written by 41 | .An Omar Polo Aq Mt op@omarpolo.com . 42 | .Sh CAVEATS 43 | .Nm 44 | attempts to convert ISO 8859-1 45 | .Pq Dq Latin 1 46 | to the current locale, but it's just a 47 | .Dq best effort 48 | stratey, as for some metadata format the original encoding is not known. 49 | -------------------------------------------------------------------------------- /songmeta/songmeta.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "log.h" 35 | #include "ogg.h" 36 | #include "songmeta.h" 37 | 38 | int printraw; 39 | 40 | static void __dead 41 | usage(void) 42 | { 43 | fprintf(stderr, "usage: %s [-r] [-g field] files...\n", 44 | getprogname()); 45 | exit(1); 46 | } 47 | 48 | int 49 | matchfield(const char *field, const char *filter) 50 | { 51 | if (filter == NULL) 52 | return (1); 53 | return (strcasecmp(field, filter) == 0); 54 | } 55 | 56 | int 57 | printfield(const char *field, const char *filter, const char *fname, 58 | int enc, const char *str) 59 | { 60 | if (!matchfield(field, filter)) 61 | return (0); 62 | 63 | if (filter == NULL) { 64 | printf("%s:\t", fname); 65 | if (strlen(fname) < 8) 66 | printf("\t"); 67 | } 68 | 69 | if (enc == ENC_GUESS) { 70 | mlprint(str); 71 | puts(""); 72 | } else 73 | printf("%s\n", str); 74 | 75 | return (0); 76 | } 77 | 78 | int 79 | readprintfield(const char *field, const char *filter, const char *fname, 80 | int enc, int fd, off_t len) 81 | { 82 | static char buf[BUFSIZ + 1]; 83 | size_t n; 84 | ssize_t r; 85 | 86 | if (!matchfield(field, filter)) 87 | return (0); 88 | 89 | if (filter == NULL) { 90 | printf("%s:\t", fname); 91 | if (strlen(fname) < 8) 92 | printf("\t"); 93 | } 94 | 95 | while (len > 0) { 96 | if ((n = len) > sizeof(buf) - 1) 97 | n = sizeof(buf) - 1; 98 | 99 | if ((r = read(fd, buf, n)) == -1) { 100 | log_warn("read"); 101 | return (-1); 102 | } 103 | if (r == 0) { 104 | log_warnx("unexpected EOF"); 105 | return (-1); 106 | } 107 | buf[r] = '\0'; 108 | 109 | if (enc == ENC_GUESS) 110 | mlprint(buf); 111 | else 112 | fwrite(buf, 1, r, stdout); 113 | 114 | len -= r; 115 | } 116 | 117 | puts(""); 118 | return (0); 119 | } 120 | 121 | static int 122 | dofile(FILE *fp, const char *name, const char *filter) 123 | { 124 | static char buf[512]; 125 | struct ogg *ogg; 126 | size_t r, ret = -1; 127 | 128 | if ((r = fread(buf, 1, sizeof(buf), fp)) < 8) { 129 | log_warn("failed to read %s", name); 130 | return (-1); 131 | } 132 | 133 | if (fseek(fp, 0, SEEK_SET) == -1) { 134 | log_warn("fseek failed in %s", name); 135 | return (-1); 136 | } 137 | 138 | if (memcmp(buf, "fLaC", 4) == 0) 139 | return flac_dump(fp, name, filter); 140 | 141 | if (memcmp(buf, "ID3", 3) == 0) 142 | return id3v2_dump(fileno(fp), name, filter); 143 | 144 | /* maybe it's an ogg file */ 145 | if ((ogg = ogg_open(fp, name)) != NULL) { 146 | if (vorbis_match(ogg) != -1) { 147 | ret = vorbis_dump(ogg, name, filter); 148 | ogg_close(ogg); 149 | return (ret); 150 | } 151 | 152 | if (ogg_rewind(ogg) == -1) { 153 | log_warn("I/O error on %s", name); 154 | ogg_close(ogg); 155 | return (-1); 156 | } 157 | 158 | if (opus_match(ogg) != -1) { 159 | ret = opus_dump(ogg, name, filter); 160 | ogg_close(ogg); 161 | return (ret); 162 | } 163 | ogg_close(ogg); 164 | } 165 | 166 | if (ferror(fp)) { 167 | log_warn("I/O error on %s", name); 168 | return (-1); 169 | } 170 | 171 | /* TODO: id3v1? */ 172 | 173 | log_warnx("unknown file format: %s", name); 174 | return (-1); 175 | } 176 | 177 | int 178 | main(int argc, char **argv) 179 | { 180 | const char *filter = NULL; 181 | FILE *fp; 182 | int ch; 183 | int ret = 0; 184 | 185 | if (pledge("stdio rpath", NULL) == -1) 186 | fatal("pledge"); 187 | 188 | log_init(1, LOG_USER); 189 | 190 | while ((ch = getopt(argc, argv, "g:r")) != -1) { 191 | switch (ch) { 192 | case 'g': 193 | filter = optarg; 194 | break; 195 | case 'r': 196 | printraw = 1; 197 | break; 198 | default: 199 | usage(); 200 | } 201 | } 202 | argc -= optind; 203 | argv += optind; 204 | 205 | if (argc == 0) 206 | usage(); 207 | 208 | for (; *argv; ++argv) { 209 | if ((fp = fopen(*argv, "r")) == NULL) { 210 | log_warn("can't open %s", *argv); 211 | ret = 1; 212 | continue; 213 | } 214 | 215 | if (argc != 1) 216 | printf("=> %s\n", *argv); 217 | if (dofile(fp, *argv, filter) == -1) 218 | ret = 1; 219 | 220 | fclose(fp); 221 | } 222 | 223 | return (ret); 224 | } 225 | -------------------------------------------------------------------------------- /songmeta/songmeta.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | extern int printraw; 27 | 28 | enum { 29 | ENC_GUESS, 30 | ENC_UTF8, 31 | }; 32 | 33 | struct ogg; 34 | 35 | /* flac.c */ 36 | int flac_dump(FILE *, const char *, const char *); 37 | 38 | /* id3v1.c */ 39 | int id3v1_dump(int, const char *, const char *); 40 | 41 | /* id3v2.c */ 42 | int id3v2_dump(int, const char *, const char *); 43 | 44 | /* opus.c */ 45 | int opus_match(struct ogg *); 46 | int opus_dump(struct ogg *, const char *, const char *); 47 | 48 | /* vorbis.c */ 49 | int vorbis_match(struct ogg *); 50 | int vorbis_dump(struct ogg *, const char *, const char *); 51 | 52 | /* songmeta.c */ 53 | int matchfield(const char *, const char *); 54 | int printfield(const char *, const char *, const char *, int, 55 | const char *); 56 | int readprintfield(const char *, const char *, const char *, 57 | int, int, off_t); 58 | 59 | /* text.c */ 60 | void mlprint(const char *); 61 | -------------------------------------------------------------------------------- /songmeta/text.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "songmeta.h" 31 | 32 | /* 33 | * The idea is to try to decode the text as UTF-8; if it fails assume 34 | * it's ISO Latin. Some strings in ISO Latin may be decoded correctly 35 | * as UTF-8 (since both are a superset of ASCII). In every case, this 36 | * is just a "best effort" for when we don't have other clues about 37 | * the format. 38 | */ 39 | static int 40 | u8decode(const char *s, int print) 41 | { 42 | wchar_t wc; 43 | int len; 44 | 45 | for (; *s != '\0'; s += len) { 46 | if ((len = mbtowc(&wc, s, MB_CUR_MAX)) == -1) { 47 | (void)mbtowc(NULL, NULL, MB_CUR_MAX); 48 | return (0); 49 | } 50 | if (print) { 51 | if (!printraw && wcwidth(wc) == -1) 52 | putchar('?'); 53 | else 54 | fwrite(s, 1, len, stdout); 55 | } 56 | } 57 | 58 | return (1); 59 | } 60 | 61 | /* Print a string that may be encoded as ISO Latin. */ 62 | void 63 | mlprint(const char *s) 64 | { 65 | wchar_t wc; 66 | 67 | if (u8decode(s, 0)) { 68 | u8decode(s, 1); 69 | return; 70 | } 71 | 72 | /* let's hope it's ISO Latin */ 73 | 74 | while (*s) { 75 | /* the first 256 UNICODE codepoints map 1:1 ISO Latin */ 76 | wc = *s++; 77 | if (!printraw && wcwidth(wc) == -1) 78 | putchar('?'); 79 | else 80 | fwprintf(stdout, L"%c", wc); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /songmeta/vorbis.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "ogg.h" 32 | #include "songmeta.h" 33 | 34 | int 35 | vorbis_match(struct ogg *ogg) 36 | { 37 | uint8_t hdr[7]; /* packet type + "vorbis" */ 38 | 39 | if (ogg_read(ogg, hdr, 7) != 7) 40 | return (-1); 41 | 42 | if (memcmp(hdr + 1, "vorbis", 6) != 0) 43 | return (-1); 44 | 45 | ogg_use_current_stream(ogg); 46 | 47 | /* 48 | * vorbis version (4 bytes) 49 | * channels (1 byte) 50 | * sample rate (4 bytes) 51 | * bitrate max/nominal/min (4 bytes each) 52 | * blocksize_{0,1} (1 byte) 53 | * framing flag (1 bit -- rounded to 1 byte) 54 | */ 55 | /* XXX check that the framing bit is 1? */ 56 | if (ogg_seek(ogg, +23) == -1) 57 | return (-1); 58 | 59 | return (0); 60 | } 61 | 62 | int 63 | vorbis_dump(struct ogg *ogg, const char *name, const char *filter) 64 | { 65 | static char buf[2048]; /* should be enough... */ 66 | char *v; 67 | uint32_t i, n, l, len; 68 | uint8_t pktype, hdr[7]; 69 | 70 | if (ogg_read(ogg, hdr, 7) != 7) 71 | return (-1); 72 | if (memcmp(hdr + 1, "vorbis", 6) != 0) 73 | return (-1); 74 | 75 | pktype = hdr[0]; 76 | if (pktype != 3) /* metadata */ 77 | return (-1); 78 | 79 | if (ogg_read(ogg, &len, sizeof(len)) != sizeof(len)) 80 | return (-1); 81 | len = le32toh(len); 82 | if (ogg_seek(ogg, +len) == -1) 83 | return (-1); 84 | 85 | if (ogg_read(ogg, &n, sizeof(n)) != sizeof(n)) 86 | return (-1); 87 | n = le32toh(n); 88 | 89 | for (i = 0; i < n; ++i) { 90 | if (ogg_read(ogg, &len, sizeof(len)) != sizeof(len)) 91 | return (-1); 92 | len = le32toh(len); 93 | 94 | l = len; 95 | if (l >= sizeof(buf)) 96 | l = len - 1; 97 | len -= l; 98 | 99 | if (ogg_read(ogg, buf, l) != l || 100 | ogg_seek(ogg, +len) == -1) 101 | return (-1); 102 | buf[l] = '\0'; 103 | 104 | if ((v = strchr(buf, '=')) == NULL) 105 | return (-1); 106 | *v++ = '\0'; 107 | 108 | printf("%s = %s\n", buf, v); 109 | } 110 | 111 | return (0); 112 | } 113 | -------------------------------------------------------------------------------- /web/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | PROG = amused-web 4 | 5 | SOURCES = web.c bufio.c http.c ws.c \ 6 | ../ev.c ../log.c ../playlist.c ../xmalloc.c 7 | 8 | OBJS = ${SOURCES:.c=.o} ${COBJS:%=../compat/%} 9 | 10 | DISTFILES = Makefile amused-web.1 bufio.c bufio.h http.c http.h \ 11 | web.c ws.c ws.h 12 | 13 | TOPDIR = .. 14 | 15 | all: ${PROG} 16 | 17 | ../config.mk ../config.h: ../configure ../tests.c 18 | @echo "$@ is out of date; please run ../configure" 19 | @exit 1 20 | 21 | include ../config.mk 22 | 23 | # --- targets --- 24 | 25 | ${PROG}: ${OBJS} 26 | ${CC} -o $@ ${OBJS} ${LDFLAGS} ${LDADD} ${LDADD_LIB_IMSG} \ 27 | ${LDADD_LIB_MD} ${LDADD_LIB_SOCKET} 28 | 29 | clean: 30 | rm -f ${OBJS} ${OBJS:.o=.d} ${PROG} 31 | 32 | distclean: clean 33 | 34 | install: 35 | mkdir -p ${DESTDIR}${BINDIR} 36 | mkdir -p ${DESTDIR}${MANDIR}/man1 37 | ${INSTALL_PROGRAM} ${PROG} ${DESTDIR}${BINDIR} 38 | ${INSTALL_MAN} amused-web.1 ${DESTDIR}${MANDIR}/man1/${PROG}.1 39 | 40 | install-local: 41 | mkdir -p ${HOME}/bin 42 | ${INSTALL_PROGRAM} ${PROG} ${HOME}/bin 43 | 44 | uninstall: 45 | rm ${DESTDIR}${BINDIR}/${PROG} 46 | rm ${DESTDIR}${MANDIR}/man1/${PROG}.1 47 | 48 | .c.o: 49 | ${CC} -I.. -I../compat ${CFLAGS} -DBUFIO_WITHOUT_TLS -c $< -o $@ 50 | 51 | # --- maintainer targets --- 52 | 53 | dist: 54 | mkdir -p ${DESTDIR}/ 55 | ${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/ 56 | 57 | # --- dependency management --- 58 | 59 | # these .d files are produced during the first build if the compiler 60 | # supports it. 61 | 62 | -include bufio.d 63 | -include http.d 64 | -include web.d 65 | -include ws.d 66 | -include ../ev.d 67 | -include ../log.d 68 | -include ../playlist.d 69 | -include ../xmalloc.d 70 | -------------------------------------------------------------------------------- /web/amused-web.1: -------------------------------------------------------------------------------- 1 | .\" Copyright (c) 2023, 2024 Omar Polo 2 | .\" 3 | .\" Permission to use, copy, modify, and distribute this software for any 4 | .\" purpose with or without fee is hereby granted, provided that the above 5 | .\" copyright notice and this permission notice appear in all copies. 6 | .\" 7 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | .\" 15 | .Dd July 30, 2024 16 | .Dt AMUSED-WEB 1 17 | .Os 18 | .Sh NAME 19 | .Nm amused-web 20 | .Nd web interface for the amused music player 21 | .Sh SYNOPSIS 22 | .Nm 23 | .Op Fl dv 24 | .Op Fl s Ar socket 25 | .Op Oo Ar host Oc Ar port 26 | .Sh DESCRIPTION 27 | .Nm 28 | is a web interface to control the 29 | .Xr amused 1 30 | music player. 31 | It exposes a web server that listen on 32 | .Ar host 33 | at the given 34 | .Ar port , 35 | by default localhost on port 9090. 36 | If 37 | .Ar host 38 | is 39 | .Sq * , 40 | which has to be escaped for the shell, 41 | then it will listen on all IPv4 and IPv6 addresses. 42 | .Pp 43 | The following options are available: 44 | .Bl -tag -width tenletters 45 | .It Fl d 46 | Do not daemonize. 47 | .Nm 48 | will run in the foreground and log to standard error. 49 | .It Fl s Ar socket 50 | Path to the 51 | .Xr amused 1 52 | control socket. 53 | By default 54 | .Pa /tmp/amused-UID 55 | is used. 56 | .It Fl v 57 | Produce more verbose output. 58 | .El 59 | .Sh ENVIRONMENT 60 | .Bl -tag -width tenletters 61 | .It Ev TEMPDIR 62 | Path to the directory where the control socket looked for. 63 | Defaults to 64 | .Pa /tmp . 65 | .El 66 | .Sh SEE ALSO 67 | .Xr amused 1 68 | .Sh AUTHORS 69 | .An -nosplit 70 | The 71 | .Nm 72 | program was written by 73 | .An Omar Polo Aq Mt op@omarpolo.com . 74 | .Sh CAVEATS 75 | .Nm 76 | should be hosted only in trusted networks because it doesn't employ all 77 | the techniques that would be needed to prevent abuses in public 78 | networks. 79 | If reachable in a public network, it should at least be protected by a 80 | password. 81 | .Pp 82 | Depending on the browser and the address from where 83 | .Nm 84 | is accessed, the use of TLS may be required. 85 | -------------------------------------------------------------------------------- /web/bufio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef BUFIO_WITHOUT_TLS 27 | struct tls; 28 | #endif 29 | 30 | #define BIO_CHUNK 128 31 | struct buf { 32 | uint8_t *buf; 33 | size_t len; 34 | size_t cap; 35 | size_t cur; 36 | }; 37 | 38 | struct bufio { 39 | int fd; 40 | int chunked; 41 | #ifndef BUFIO_WITHOUT_TLS 42 | struct tls *ctx; 43 | #endif 44 | int wantev; 45 | struct buf wbuf; 46 | struct buf rbuf; 47 | }; 48 | 49 | #define BUFIO_WANT_READ 0x1 50 | #define BUFIO_WANT_WRITE 0x2 51 | 52 | int buf_init(struct buf *); 53 | int buf_append(struct buf *, const void *, size_t); 54 | int buf_has_line(struct buf *, const char *); 55 | char *buf_getdelim(struct buf *, const char *, size_t *); 56 | void buf_drain(struct buf *, size_t); 57 | void buf_drain_line(struct buf *, const char *); 58 | void buf_free(struct buf *); 59 | 60 | int bufio_init(struct bufio *); 61 | void bufio_free(struct bufio *); 62 | int bufio_close(struct bufio *); 63 | int bufio_reset(struct bufio *); 64 | void bufio_set_fd(struct bufio *, int); 65 | void bufio_set_chunked(struct bufio *, int); 66 | int bufio_starttls(struct bufio *, const char *, int, 67 | const uint8_t *, size_t, const uint8_t *, size_t); 68 | int bufio_ev(struct bufio *); 69 | int bufio_handshake(struct bufio *); 70 | ssize_t bufio_read(struct bufio *); 71 | size_t bufio_drain(struct bufio *, void *, size_t); 72 | ssize_t bufio_write(struct bufio *); 73 | const char *bufio_io_err(struct bufio *); 74 | int bufio_compose(struct bufio *, const void *, size_t); 75 | int bufio_compose_str(struct bufio *, const char *); 76 | int bufio_compose_fmt(struct bufio *, const char *, ...) 77 | __attribute__((__format__ (printf, 2, 3))); 78 | void bufio_rewind_cursor(struct bufio *); 79 | 80 | /* callbacks for pdjson */ 81 | int bufio_get_cb(void *); 82 | int bufio_peek_cb(void *); 83 | -------------------------------------------------------------------------------- /web/http.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | enum http_method { 27 | METHOD_UNKNOWN, 28 | METHOD_GET, 29 | METHOD_POST, 30 | }; 31 | 32 | enum http_version { 33 | HTTP_1_0, 34 | HTTP_1_1, 35 | }; 36 | 37 | struct bufio; 38 | 39 | struct request { 40 | char *path; 41 | int method; 42 | int version; 43 | char *secret; 44 | char *ctype; 45 | char *body; 46 | size_t clen; 47 | 48 | #define R_CONNUPGR 0x01 49 | #define R_UPGRADEWS 0x02 50 | #define R_WSVERSION 0x04 51 | int flags; 52 | }; 53 | 54 | struct client; 55 | typedef void (*route_fn)(struct client *); 56 | 57 | TAILQ_HEAD(clthead, client); 58 | struct client { 59 | char *buf; 60 | size_t len; 61 | size_t cap; 62 | struct bufio bio; 63 | struct request req; 64 | int err; 65 | int chunked; 66 | int ws; /* if talking ws:// */ 67 | int reqdone; /* done parsing the request */ 68 | int done; /* done handling the client */ 69 | route_fn route; 70 | 71 | TAILQ_ENTRY(client) clients; 72 | }; 73 | 74 | int http_init(struct client *, int); 75 | int http_parse(struct client *); 76 | int http_read(struct client *); 77 | void http_postdata(struct client *, char **, size_t *); 78 | int http_reply(struct client *, int, const char *, const char *); 79 | int http_flush(struct client *); 80 | int http_write(struct client *, const char *, size_t); 81 | int http_writes(struct client *, const char *); 82 | int http_fmt(struct client *, const char *, ...); 83 | int http_urlescape(struct client *, const char *); 84 | int http_htmlescape(struct client *, const char *); 85 | int http_close(struct client *); 86 | void http_free(struct client *); 87 | -------------------------------------------------------------------------------- /web/ws.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "bufio.h" 36 | #include "http.h" 37 | #include "ws.h" 38 | 39 | #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 40 | 41 | static int 42 | tob64(unsigned char ch) 43 | { 44 | if (ch < 26) 45 | return ('A' + ch); 46 | ch -= 26; 47 | if (ch < 26) 48 | return ('a' + ch); 49 | ch -= 26; 50 | if (ch < 10) 51 | return ('0' + ch); 52 | ch -= 10; 53 | if (ch == 0) 54 | return ('+'); 55 | if (ch == 1) 56 | return ('/'); 57 | errno = EINVAL; 58 | return (-1); 59 | } 60 | 61 | static int 62 | b64encode(unsigned char *in, size_t ilen, char *out, size_t olen) 63 | { 64 | int r; 65 | 66 | #define SET(x) { \ 67 | if ((r = tob64((x) & 0x3F)) == -1) \ 68 | return (-1); \ 69 | *out++ = r; \ 70 | } 71 | 72 | while (ilen > 0) { 73 | if (olen < 4) { 74 | errno = ENOSPC; 75 | return (-1); 76 | } 77 | olen -= 4; 78 | 79 | switch (ilen) { 80 | case 1: 81 | SET(in[0] >> 2); 82 | SET(in[0] << 4); 83 | *out++ = '='; 84 | *out++ = '='; 85 | ilen = 0; 86 | break; 87 | case 2: 88 | SET(in[0] >> 2); 89 | SET(in[0] << 4 | in[1] >> 4); 90 | SET(in[1] << 2); 91 | *out++ = '='; 92 | ilen = 0; 93 | break; 94 | default: 95 | SET(in[0] >> 2); 96 | SET(in[0] << 4 | in[1] >> 4); 97 | SET(in[1] << 2 | in[2] >> 6); 98 | SET(in[2]); 99 | ilen -= 3; 100 | in += 3; 101 | break; 102 | } 103 | } 104 | 105 | #undef SET 106 | 107 | if (olen < 1) { 108 | errno = ENOSPC; 109 | return (-1); 110 | } 111 | *out = '\0'; 112 | return (0); 113 | } 114 | 115 | int 116 | ws_accept_hdr(const char *secret, char *out, size_t olen) 117 | { 118 | SHA1_CTX ctx; 119 | uint8_t hash[SHA1_DIGEST_LENGTH]; 120 | 121 | SHA1Init(&ctx); 122 | SHA1Update(&ctx, secret, strlen(secret)); 123 | SHA1Update(&ctx, WS_GUID, strlen(WS_GUID)); 124 | SHA1Final(hash, &ctx); 125 | 126 | return (b64encode(hash, sizeof(hash), out, olen)); 127 | } 128 | 129 | int 130 | ws_read(struct client *clt, int *type, size_t *len) 131 | { 132 | struct buf *rbuf = &clt->bio.rbuf; 133 | size_t i; 134 | uint32_t mask; 135 | uint8_t first, second, op, plen; 136 | 137 | *type = WST_UNKNOWN, *len = 0; 138 | 139 | if (rbuf->len < 2) { 140 | errno = EAGAIN; 141 | return (-1); 142 | } 143 | 144 | memcpy(&first, &rbuf->buf[0], sizeof(first)); 145 | memcpy(&second, &rbuf->buf[1], sizeof(second)); 146 | 147 | /* for the close message this doesn't seem to be the case... */ 148 | #if 0 149 | /* the reserved bits must be zero, don't care about FIN */ 150 | if ((first & 0x0E) != 0) { 151 | errno = EINVAL; 152 | return (-1); 153 | } 154 | #endif 155 | 156 | /* mask must be set for messages sent by the clients */ 157 | if ((second >> 7) != 1) { 158 | errno = EINVAL; 159 | return (-1); 160 | } 161 | 162 | op = first & 0x0F; 163 | plen = second & 0x7F; 164 | 165 | /* don't support extended payload length for now */ 166 | if (plen >= 126) { 167 | errno = E2BIG; 168 | return (-1); 169 | } 170 | 171 | *len = plen; 172 | 173 | switch (op) { 174 | case WST_CONT: 175 | case WST_TEXT: 176 | case WST_BINARY: 177 | case WST_CLOSE: 178 | case WST_PING: 179 | *type = op; 180 | break; 181 | } 182 | 183 | if (rbuf->len < sizeof(first) + sizeof(second) + sizeof(mask) + plen) { 184 | errno = EAGAIN; 185 | return (-1); 186 | } 187 | 188 | buf_drain(rbuf, 2); /* header */ 189 | memcpy(&mask, rbuf->buf, sizeof(mask)); 190 | buf_drain(rbuf, 4); 191 | 192 | /* decode the payload */ 193 | for (i = 0; i < plen; ++i) 194 | rbuf->buf[i] ^= mask >> (8 * (i % 4)); 195 | 196 | return (0); 197 | } 198 | 199 | int 200 | ws_compose(struct client *clt, int type, const void *data, size_t len) 201 | { 202 | struct bufio *bio = &clt->bio; 203 | uint16_t extlen = 0; 204 | uint8_t first, second; 205 | 206 | first = (type & 0x0F) | 0x80; 207 | 208 | if (len < 126) 209 | second = len; 210 | else { 211 | second = 126; 212 | 213 | /* 214 | * for the extended length, the most significant bit 215 | * must be zero. We could use the 64 bit field but 216 | * it's a waste. 217 | */ 218 | if (len > 0x7FFF) { 219 | errno = ERANGE; 220 | return (-1); 221 | } 222 | extlen = htons(len); 223 | } 224 | 225 | if (bufio_compose(bio, &first, 1) == -1 || 226 | bufio_compose(bio, &second, 1) == -1) 227 | goto err; 228 | 229 | if (extlen != 0 && bufio_compose(bio, &extlen, sizeof(extlen)) == -1) 230 | goto err; 231 | 232 | if (bufio_compose(bio, data, len) == -1) 233 | goto err; 234 | 235 | return (0); 236 | 237 | err: 238 | clt->err = 1; 239 | return (-1); 240 | } 241 | -------------------------------------------------------------------------------- /web/ws.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * Anyone is free to copy, modify, publish, use, compile, sell, or 5 | * distribute this software, either in source code form or as a compiled 6 | * binary, for any purpose, commercial or non-commercial, and by any 7 | * means. 8 | * 9 | * In jurisdictions that recognize copyright laws, the author or authors 10 | * of this software dedicate any and all copyright interest in the 11 | * software to the public domain. We make this dedication for the benefit 12 | * of the public at large and to the detriment of our heirs and 13 | * successors. We intend this dedication to be an overt act of 14 | * relinquishment in perpetuity of all present and future rights to this 15 | * software under copyright law. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | enum { 27 | WST_UNKNOWN = -1, 28 | WST_CONT = 0x00, 29 | WST_TEXT = 0x01, 30 | WST_BINARY = 0x02, 31 | WST_CLOSE = 0x08, 32 | WST_PING = 0x09, 33 | WST_PONG = 0x0A, 34 | }; 35 | 36 | struct client; 37 | 38 | int ws_accept_hdr(const char *, char *, size_t); 39 | int ws_read(struct client *, int *, size_t *); 40 | int ws_compose(struct client *, int, const void *, size_t); 41 | -------------------------------------------------------------------------------- /xmalloc.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: xmalloc.c,v 1.4 2019/06/28 05:44:09 deraadt Exp $ */ 2 | /* 3 | * Author: Tatu Ylonen 4 | * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland 5 | * All rights reserved 6 | * Versions of malloc and friends that check their results, and never return 7 | * failure (they call fatal if they encounter an error). 8 | * 9 | * As far as I am concerned, the code I have written for this software 10 | * can be used freely for any purpose. Any derived versions of this 11 | * software must be clearly marked as such, and if the derived work is 12 | * incompatible with the protocol description in the RFC file, it must be 13 | * called by a name other than "ssh" or "Secure Shell". 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "log.h" 24 | #include "xmalloc.h" 25 | 26 | void * 27 | xmalloc(size_t size) 28 | { 29 | void *ptr; 30 | 31 | if (size == 0) 32 | fatal("xmalloc: zero size"); 33 | ptr = malloc(size); 34 | if (ptr == NULL) 35 | fatal("xmalloc: allocating %zu bytes", size); 36 | return ptr; 37 | } 38 | 39 | void * 40 | xcalloc(size_t nmemb, size_t size) 41 | { 42 | void *ptr; 43 | 44 | if (size == 0 || nmemb == 0) 45 | fatal("xcalloc: zero size"); 46 | ptr = calloc(nmemb, size); 47 | if (ptr == NULL) 48 | fatal("xcalloc: allocating %zu * %zu bytes", nmemb, size); 49 | return ptr; 50 | } 51 | 52 | void * 53 | xreallocarray(void *ptr, size_t nmemb, size_t size) 54 | { 55 | void *new_ptr; 56 | 57 | new_ptr = reallocarray(ptr, nmemb, size); 58 | if (new_ptr == NULL) 59 | fatal("xreallocarray: allocating %zu * %zu bytes", 60 | nmemb, size); 61 | return new_ptr; 62 | } 63 | 64 | void * 65 | xrecallocarray(void *ptr, size_t oldnmemb, size_t nmemb, size_t size) 66 | { 67 | void *new_ptr; 68 | 69 | new_ptr = recallocarray(ptr, oldnmemb, nmemb, size); 70 | if (new_ptr == NULL) 71 | fatal("xrecallocarray: allocating %zu * %zu bytes", 72 | nmemb, size); 73 | return new_ptr; 74 | } 75 | 76 | char * 77 | xstrdup(const char *str) 78 | { 79 | char *cp; 80 | 81 | if ((cp = strdup(str)) == NULL) 82 | fatal("xstrdup"); 83 | return cp; 84 | } 85 | 86 | int 87 | xasprintf(char **ret, const char *fmt, ...) 88 | { 89 | va_list ap; 90 | int i; 91 | 92 | va_start(ap, fmt); 93 | i = vasprintf(ret, fmt, ap); 94 | va_end(ap); 95 | 96 | if (i == -1) 97 | fatal("xasprintf"); 98 | 99 | return i; 100 | } 101 | -------------------------------------------------------------------------------- /xmalloc.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: xmalloc.h,v 1.3 2015/11/17 18:25:03 tobias Exp $ */ 2 | 3 | /* 4 | * Author: Tatu Ylonen 5 | * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland 6 | * All rights reserved 7 | * Created: Mon Mar 20 22:09:17 1995 ylo 8 | * 9 | * Versions of malloc and friends that check their results, and never return 10 | * failure (they call fatal if they encounter an error). 11 | * 12 | * As far as I am concerned, the code I have written for this software 13 | * can be used freely for any purpose. Any derived versions of this 14 | * software must be clearly marked as such, and if the derived work is 15 | * incompatible with the protocol description in the RFC file, it must be 16 | * called by a name other than "ssh" or "Secure Shell". 17 | */ 18 | 19 | #ifndef XMALLOC_H 20 | #define XMALLOC_H 21 | 22 | void *xmalloc(size_t); 23 | void *xcalloc(size_t, size_t); 24 | void *xreallocarray(void *, size_t, size_t); 25 | void *xrecallocarray(void *, size_t, size_t, size_t); 26 | char *xstrdup(const char *); 27 | int xasprintf(char **, const char *, ...) 28 | __attribute__((__format__ (printf, 2, 3))) 29 | __attribute__((__nonnull__ (2))); 30 | 31 | #endif /* XMALLOC_H */ 32 | --------------------------------------------------------------------------------