1/*	$OpenBSD: midicat.c,v 1.7 2022/12/02 22:36:34 cheloha Exp $	*/
2/*
3 * Copyright (c) 2015 Alexandre Ratchov <alex@caoua.org>
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#include <err.h>
18#include <fcntl.h>
19#include <sndio.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <unistd.h>
23#include <string.h>
24
25void __dead usage(void);
26
27int
28main(int argc, char **argv)
29{
30#define MIDI_BUFSZ	1024
31	unsigned char buf[MIDI_BUFSZ];
32	struct mio_hdl *ih, *oh;
33	char *port0, *port1, *ifile, *ofile;
34	int ifd, ofd;
35	int dump, c, i, len, n, sep, mode;
36
37	dump = 0;
38	port0 = port1 = ifile = ofile = NULL;
39	ih = oh = NULL;
40	ifd = ofd = -1;
41
42	while ((c = getopt(argc, argv, "di:o:q:")) != -1) {
43		switch (c) {
44		case 'd':
45			dump = 1;
46			break;
47		case 'q':
48			if (port0 == NULL)
49				port0 = optarg;
50			else if (port1 == NULL)
51				port1 = optarg;
52			else
53				errx(1, "too many -q options");
54			break;
55		case 'i':
56			ifile = optarg;
57			break;
58		case 'o':
59			ofile = optarg;
60			break;
61		default:
62			usage();
63		}
64	}
65	argc -= optind;
66	argv += optind;
67
68	if (argc != 0)
69		usage();
70
71	/* we don't support more than one data flow */
72	if (ifile != NULL && ofile != NULL)
73		errx(1, "-i and -o are exclusive");
74
75	/* second port makes sense only for port-to-port transfers */
76	if (port1 != NULL && !(ifile == NULL && ofile == NULL))
77		errx(1, "too many -q options");
78
79	/* if there're neither files nor ports, then we've nothing to do */
80	if (port0 == NULL && ifile == NULL && ofile == NULL)
81		usage();
82
83	/* if no port specified, use default one */
84	if (port0 == NULL)
85		port0 = MIO_PORTANY;
86
87	/* open input or output file (if any) */
88	if (ifile) {
89		if (strcmp(ifile, "-") == 0) {
90			ifile = "stdin";
91			ifd = STDIN_FILENO;
92		} else {
93			ifd = open(ifile, O_RDONLY);
94			if (ifd == -1)
95				err(1, "%s", ifile);
96		}
97	} else if (ofile) {
98		if (strcmp(ofile, "-") == 0) {
99			ofile = "stdout";
100			ofd = STDOUT_FILENO;
101		} else {
102			ofd = open(ofile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
103			if (ofd == -1)
104				err(1, "%s", ofile);
105		}
106	}
107
108	/* open first port for input and output (if output needed) */
109	if (ofile)
110		mode = MIO_IN;
111	else if (ifile)
112		mode = MIO_OUT;
113	else if (port1 == NULL)
114		mode = MIO_IN | MIO_OUT;
115	else
116		mode = MIO_IN;
117	ih = mio_open(port0, mode, 0);
118	if (ih == NULL)
119		errx(1, "%s: couldn't open port", port0);
120
121	/* open second port, output only */
122	if (port1 == NULL)
123		oh = ih;
124	else {
125		oh = mio_open(port1, MIO_OUT, 0);
126		if (oh == NULL)
127			errx(1, "%s: couldn't open port", port1);
128	}
129
130	if (pledge("stdio", NULL) == -1)
131		err(1, "pledge");
132
133	/* transfer until end-of-file or error */
134	for (;;) {
135		if (ifile != NULL) {
136			len = read(ifd, buf, sizeof(buf));
137			if (len == 0)
138				break;
139			if (len == -1) {
140				warn("%s", ifile);
141				break;
142			}
143		} else {
144			len = mio_read(ih, buf, sizeof(buf));
145			if (len == 0) {
146				warnx("%s: disconnected", port0);
147				break;
148			}
149		}
150		if (ofile != NULL) {
151			n = write(ofd, buf, len);
152			if (n != len) {
153				warn("%s: short write", ofile);
154				break;
155			}
156		} else {
157			n = mio_write(oh, buf, len);
158			if (n != len) {
159				warnx("%s: disconnected", port1);
160				break;
161			}
162		}
163		if (dump) {
164			for (i = 0; i < len; i++) {
165				sep = (i % 16 == 15 || i == len - 1) ?
166				    '\n' : ' ';
167				fprintf(stderr, "%02x%c", buf[i], sep);
168			}
169		}
170	}
171
172	/* clean-up */
173	if (port0)
174		mio_close(ih);
175	if (port1)
176		mio_close(oh);
177	if (ifile)
178		close(ifd);
179	if (ofile)
180		close(ofd);
181	return 0;
182}
183
184void __dead
185usage(void)
186{
187	fprintf(stderr, "usage: midicat [-d] [-i in-file] [-o out-file] "
188	    "[-q in-port] [-q out-port]\n");
189	exit(1);
190}
191