Annotation of src/usr.bin/sndiod/sndiod.c, Revision 1.21
1.18 ratchov 1: /* $OpenBSD$ */
1.1 ratchov 2: /*
3: * Copyright (c) 2008-2012 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 <sys/queue.h>
18: #include <sys/stat.h>
19: #include <sys/types.h>
20: #include <sys/resource.h>
1.18 ratchov 21: #include <sys/socket.h>
1.1 ratchov 22:
23: #include <err.h>
24: #include <errno.h>
25: #include <fcntl.h>
26: #include <grp.h>
27: #include <limits.h>
28: #include <pwd.h>
29: #include <signal.h>
30: #include <sndio.h>
31: #include <stdio.h>
32: #include <stdlib.h>
33: #include <string.h>
34: #include <unistd.h>
35:
36: #include "amsg.h"
37: #include "defs.h"
38: #include "dev.h"
1.18 ratchov 39: #include "fdpass.h"
1.1 ratchov 40: #include "file.h"
41: #include "listen.h"
42: #include "midi.h"
43: #include "opt.h"
44: #include "sock.h"
45: #include "utils.h"
46:
47: /*
48: * unprivileged user name
49: */
50: #ifndef SNDIO_USER
51: #define SNDIO_USER "_sndio"
52: #endif
53:
54: /*
1.18 ratchov 55: * privileged user name
56: */
57: #ifndef SNDIO_PRIV_USER
58: #define SNDIO_PRIV_USER "_sndiop"
59: #endif
60:
61: /*
1.1 ratchov 62: * priority when run as root
63: */
64: #ifndef SNDIO_PRIO
65: #define SNDIO_PRIO (-20)
66: #endif
67:
68: /*
69: * sample rate if no ``-r'' is used
70: */
71: #ifndef DEFAULT_RATE
72: #define DEFAULT_RATE 48000
73: #endif
74:
75: /*
76: * block size if neither ``-z'' nor ``-b'' is used
77: */
78: #ifndef DEFAULT_ROUND
79: #define DEFAULT_ROUND 960
80: #endif
81:
82: /*
83: * buffer size if neither ``-z'' nor ``-b'' is used
84: */
85: #ifndef DEFAULT_BUFSZ
1.8 dcoppa 86: #define DEFAULT_BUFSZ 7680
1.1 ratchov 87: #endif
88:
89: /*
90: * default device in server mode
91: */
92: #ifndef DEFAULT_DEV
93: #define DEFAULT_DEV "rsnd/0"
94: #endif
1.5 ratchov 95:
96: void sigint(int);
97: void opt_ch(int *, int *);
98: void opt_enc(struct aparams *);
99: int opt_mmc(void);
100: int opt_onoff(void);
101: int getword(char *, char **);
102: unsigned int opt_mode(void);
103: void getbasepath(char *, size_t);
104: void setsig(void);
105: void unsetsig(void);
106: struct dev *mkdev(char *, struct aparams *,
107: int, int, int, int, int, int);
108: struct opt *mkopt(char *, struct dev *,
109: int, int, int, int, int, int, int, int);
1.1 ratchov 110:
111: unsigned int log_level = 0;
112: volatile sig_atomic_t quit_flag = 0;
113:
114: char usagestr[] = "usage: sndiod [-d] [-a flag] [-b nframes] "
115: "[-C min:max] [-c min:max] [-e enc]\n\t"
116: "[-f device] [-j flag] [-L addr] [-m mode] [-q port] [-r rate]\n\t"
117: "[-s name] [-t mode] [-U unit] [-v volume] [-w flag] [-z nframes]\n";
118:
119: /*
120: * SIGINT handler, it raises the quit flag. If the flag is already set,
121: * that means that the last SIGINT was not handled, because the process
122: * is blocked somewhere, so exit.
123: */
124: void
125: sigint(int s)
126: {
127: if (quit_flag)
128: _exit(1);
129: quit_flag = 1;
130: }
131:
132: void
133: opt_ch(int *rcmin, int *rcmax)
134: {
135: char *next, *end;
136: long cmin, cmax;
137:
138: errno = 0;
139: cmin = strtol(optarg, &next, 10);
140: if (next == optarg || *next != ':')
141: goto failed;
142: cmax = strtol(++next, &end, 10);
143: if (end == next || *end != '\0')
144: goto failed;
145: if (cmin < 0 || cmax < cmin || cmax >= NCHAN_MAX)
146: goto failed;
147: *rcmin = cmin;
148: *rcmax = cmax;
149: return;
150: failed:
151: errx(1, "%s: bad channel range", optarg);
152: }
153:
154: void
155: opt_enc(struct aparams *par)
156: {
157: int len;
158:
159: len = aparams_strtoenc(par, optarg);
160: if (len == 0 || optarg[len] != '\0')
161: errx(1, "%s: bad encoding", optarg);
162: }
163:
164: int
165: opt_mmc(void)
166: {
167: if (strcmp("off", optarg) == 0)
168: return 0;
169: if (strcmp("slave", optarg) == 0)
170: return 1;
171: errx(1, "%s: off/slave expected", optarg);
172: }
173:
174: int
175: opt_onoff(void)
176: {
177: if (strcmp("off", optarg) == 0)
178: return 0;
179: if (strcmp("on", optarg) == 0)
180: return 1;
181: errx(1, "%s: on/off expected", optarg);
182: }
183:
1.4 ratchov 184: int
185: getword(char *word, char **str)
186: {
187: char *p = *str;
188:
189: for (;;) {
190: if (*word == '\0')
191: break;
192: if (*word++ != *p++)
193: return 0;
194: }
195: if (*p == ',' || *p == '\0') {
196: *str = p;
197: return 1;
198: }
199: return 0;
200: }
201:
1.1 ratchov 202: unsigned int
203: opt_mode(void)
204: {
205: unsigned int mode = 0;
206: char *p = optarg;
207:
1.4 ratchov 208: for (;;) {
209: if (getword("play", &p)) {
1.1 ratchov 210: mode |= MODE_PLAY;
1.4 ratchov 211: } else if (getword("rec", &p)) {
1.1 ratchov 212: mode |= MODE_REC;
1.4 ratchov 213: } else if (getword("mon", &p)) {
1.1 ratchov 214: mode |= MODE_MON;
1.4 ratchov 215: } else if (getword("midi", &p)) {
1.1 ratchov 216: mode |= MODE_MIDIMASK;
1.4 ratchov 217: } else
1.1 ratchov 218: errx(1, "%s: bad mode", optarg);
219: if (*p == '\0')
220: break;
1.4 ratchov 221: p++;
1.1 ratchov 222: }
223: if (mode == 0)
224: errx(1, "empty mode");
225: return mode;
226: }
227:
228: void
229: setsig(void)
230: {
231: struct sigaction sa;
232:
233: quit_flag = 0;
234: sigfillset(&sa.sa_mask);
235: sa.sa_flags = SA_RESTART;
236: sa.sa_handler = sigint;
237: if (sigaction(SIGINT, &sa, NULL) < 0)
238: err(1, "sigaction(int) failed");
239: if (sigaction(SIGTERM, &sa, NULL) < 0)
240: err(1, "sigaction(term) failed");
241: if (sigaction(SIGHUP, &sa, NULL) < 0)
242: err(1, "sigaction(hup) failed");
243: }
244:
245: void
246: unsetsig(void)
247: {
248: struct sigaction sa;
249:
250: sigfillset(&sa.sa_mask);
251: sa.sa_flags = SA_RESTART;
252: sa.sa_handler = SIG_DFL;
253: if (sigaction(SIGHUP, &sa, NULL) < 0)
254: err(1, "unsetsig(hup): sigaction failed\n");
255: if (sigaction(SIGTERM, &sa, NULL) < 0)
256: err(1, "unsetsig(term): sigaction failed\n");
257: if (sigaction(SIGINT, &sa, NULL) < 0)
258: err(1, "unsetsig(int): sigaction failed\n");
259: }
260:
261: void
262: getbasepath(char *base, size_t size)
263: {
264: uid_t uid;
265: struct stat sb;
1.14 ratchov 266: mode_t mask, omask;
1.1 ratchov 267:
268: uid = geteuid();
269: if (uid == 0) {
270: mask = 022;
1.10 ratchov 271: snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR);
1.1 ratchov 272: } else {
273: mask = 077;
1.10 ratchov 274: snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR "-%u", uid);
1.1 ratchov 275: }
1.14 ratchov 276: omask = umask(mask);
277: if (mkdir(base, 0777) < 0) {
1.1 ratchov 278: if (errno != EEXIST)
279: err(1, "mkdir(\"%s\")", base);
280: }
1.14 ratchov 281: umask(omask);
1.1 ratchov 282: if (stat(base, &sb) < 0)
283: err(1, "stat(\"%s\")", base);
284: if (sb.st_uid != uid || (sb.st_mode & mask) != 0)
285: errx(1, "%s has wrong permissions", base);
286: }
287:
288: struct dev *
289: mkdev(char *path, struct aparams *par,
290: int mode, int bufsz, int round, int rate, int hold, int autovol)
291: {
292: struct dev *d;
293:
294: for (d = dev_list; d != NULL; d = d->next) {
295: if (strcmp(d->path, path) == 0)
296: return d;
297: }
298: if (!bufsz && !round) {
299: round = DEFAULT_ROUND;
300: bufsz = DEFAULT_BUFSZ;
301: } else if (!bufsz) {
302: bufsz = round * 2;
303: } else if (!round)
304: round = bufsz / 2;
305: d = dev_new(path, par, mode, bufsz, round, rate, hold, autovol);
306: if (d == NULL)
307: exit(1);
308: return d;
309: }
310:
311: struct opt *
312: mkopt(char *path, struct dev *d,
313: int pmin, int pmax, int rmin, int rmax,
314: int mode, int vol, int mmc, int dup)
315: {
316: struct opt *o;
317:
318: o = opt_new(path, d, pmin, pmax, rmin, rmax,
319: MIDI_TO_ADATA(vol), mmc, dup, mode);
320: if (o == NULL)
321: errx(1, "%s: couldn't create subdev", path);
322: dev_adjpar(d, o->mode, o->pmin, o->pmax, o->rmin, o->rmax);
323: return o;
324: }
325:
326: int
327: main(int argc, char **argv)
328: {
329: int c, background, unit;
330: int pmin, pmax, rmin, rmax;
1.13 ratchov 331: char base[SOCKPATH_MAX], path[SOCKPATH_MAX], *tcpaddr;
1.1 ratchov 332: unsigned int mode, dup, mmc, vol;
333: unsigned int hold, autovol, bufsz, round, rate;
334: const char *str;
335: struct aparams par;
336: struct dev *d;
337: struct port *p;
338: struct listen *l;
1.17 ratchov 339: struct passwd *pw;
1.18 ratchov 340: int s[2];
341: pid_t pid;
1.20 ratchov 342: uid_t euid, hpw_uid, wpw_uid;
343: gid_t hpw_gid, wpw_gid;
344: char *wpw_dir;
1.1 ratchov 345:
346: atexit(log_flush);
347:
348: /*
349: * global options defaults
350: */
351: vol = 118;
352: dup = 1;
353: mmc = 0;
354: hold = 0;
355: autovol = 1;
356: bufsz = 0;
357: round = 0;
358: rate = DEFAULT_RATE;
359: unit = 0;
360: background = 1;
361: pmin = 0;
362: pmax = 1;
363: rmin = 0;
364: rmax = 1;
365: aparams_init(&par);
366: mode = MODE_PLAY | MODE_REC;
1.13 ratchov 367: tcpaddr = NULL;
1.1 ratchov 368:
1.15 ratchov 369: while ((c = getopt(argc, argv, "a:b:c:C:de:f:j:L:m:q:r:s:t:U:v:w:x:z:")) != -1) {
1.1 ratchov 370: switch (c) {
371: case 'd':
372: log_level++;
373: background = 0;
374: break;
375: case 'U':
376: unit = strtonum(optarg, 0, 15, &str);
377: if (str)
378: errx(1, "%s: unit number is %s", optarg, str);
379: break;
380: case 'L':
1.13 ratchov 381: tcpaddr = optarg;
1.1 ratchov 382: break;
383: case 'm':
384: mode = opt_mode();
385: break;
386: case 'j':
387: dup = opt_onoff();
388: break;
389: case 't':
390: mmc = opt_mmc();
391: break;
392: case 'c':
393: opt_ch(&pmin, &pmax);
394: break;
395: case 'C':
396: opt_ch(&rmin, &rmax);
397: break;
398: case 'e':
399: opt_enc(&par);
400: break;
401: case 'r':
402: rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str);
403: if (str)
404: errx(1, "%s: rate is %s", optarg, str);
405: break;
406: case 'v':
407: vol = strtonum(optarg, 0, MIDI_MAXCTL, &str);
408: if (str)
409: errx(1, "%s: volume is %s", optarg, str);
410: break;
411: case 's':
412: if ((d = dev_list) == NULL) {
413: d = mkdev(DEFAULT_DEV, &par, 0, bufsz, round, rate,
414: hold, autovol);
415: }
416: mkopt(optarg, d, pmin, pmax, rmin, rmax,
417: mode, vol, mmc, dup);
418: break;
419: case 'q':
1.3 ratchov 420: p = port_new(optarg, MODE_MIDIMASK, hold);
1.1 ratchov 421: if (!p)
422: errx(1, "%s: can't open port", optarg);
423: break;
424: case 'a':
425: hold = opt_onoff();
426: break;
427: case 'w':
428: autovol = opt_onoff();
429: break;
430: case 'b':
431: bufsz = strtonum(optarg, 1, RATE_MAX, &str);
432: if (str)
433: errx(1, "%s: buffer size is %s", optarg, str);
434: break;
435: case 'z':
436: round = strtonum(optarg, 1, SHRT_MAX, &str);
437: if (str)
438: errx(1, "%s: block size is %s", optarg, str);
439: break;
440: case 'f':
441: mkdev(optarg, &par, 0, bufsz, round, rate, hold, autovol);
442: break;
443: default:
444: fputs(usagestr, stderr);
445: return 1;
446: }
447: }
448: argc -= optind;
449: argv += optind;
450: if (argc > 0) {
451: fputs(usagestr, stderr);
452: return 1;
453: }
454: if (dev_list == NULL)
455: mkdev(DEFAULT_DEV, &par, 0, bufsz, round, rate, hold, autovol);
456: for (d = dev_list; d != NULL; d = d->next) {
457: if (opt_byname("default", d->num))
458: continue;
459: mkopt("default", d, pmin, pmax, rmin, rmax,
460: mode, vol, mmc, dup);
461: }
1.17 ratchov 462:
463: setsig();
464: filelist_init();
465:
1.20 ratchov 466: euid = geteuid();
467: if (euid == 0) {
468: if ((pw = getpwnam(SNDIO_PRIV_USER)) == NULL)
469: errx(1, "unknown user %s", SNDIO_PRIV_USER);
470: hpw_uid = pw->pw_uid;
471: hpw_gid = pw->pw_gid;
472: if ((pw = getpwnam(SNDIO_USER)) == NULL)
473: errx(1, "unknown user %s", SNDIO_USER);
474: wpw_uid = pw->pw_uid;
475: wpw_gid = pw->pw_gid;
476: wpw_dir = xstrdup(pw->pw_dir);
477: } else {
1.21 ! ratchov 478: hpw_uid = wpw_uid = hpw_gid = wpw_gid = 0xdeadbeef;
! 479: wpw_dir = NULL;
1.20 ratchov 480: }
481:
1.18 ratchov 482: /* start subprocesses */
483:
484: if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) < 0) {
485: perror("socketpair");
486: return 1;
1.13 ratchov 487: }
1.18 ratchov 488: pid = fork();
489: if (pid == -1) {
490: log_puts("can't fork\n");
491: return 1;
1.1 ratchov 492: }
1.18 ratchov 493: if (pid == 0) {
494: setproctitle("helper");
495: close(s[0]);
496: if (fdpass_new(s[1], &helper_fileops) == NULL)
1.1 ratchov 497: return 1;
1.18 ratchov 498: if (background) {
499: log_flush();
500: log_level = 0;
501: if (daemon(0, 0) < 0)
502: err(1, "daemon");
503: }
1.20 ratchov 504: if (euid == 0) {
505: if (setgroups(1, &hpw_gid) ||
506: setresgid(hpw_gid, hpw_gid, hpw_gid) ||
507: setresuid(hpw_uid, hpw_uid, hpw_uid))
1.18 ratchov 508: err(1, "cannot drop privileges");
509: }
510: while (file_poll())
511: ; /* nothing */
512: } else {
513: close(s[1]);
514: if (fdpass_new(s[0], &worker_fileops) == NULL)
1.1 ratchov 515: return 1;
1.18 ratchov 516:
517: getbasepath(base, sizeof(base));
518: snprintf(path,
519: SOCKPATH_MAX, "%s/" SOCKPATH_FILE "%u",
520: base, unit);
521: listen_new_un(path);
522: if (tcpaddr)
523: listen_new_tcp(tcpaddr, AUCAT_PORT + unit);
524: for (l = listen_list; l != NULL; l = l->next) {
525: if (!listen_init(l))
526: return 1;
527: }
528:
529: midi_init();
530: for (p = port_list; p != NULL; p = p->next) {
531: if (!port_init(p))
532: return 1;
533: }
534: for (d = dev_list; d != NULL; d = d->next) {
535: if (!dev_init(d))
536: return 1;
537: }
538: if (background) {
539: log_flush();
540: log_level = 0;
541: if (daemon(0, 0) < 0)
542: err(1, "daemon");
543: }
1.20 ratchov 544: if (euid == 0) {
1.18 ratchov 545: if (setpriority(PRIO_PROCESS, 0, SNDIO_PRIO) < 0)
546: err(1, "setpriority");
1.20 ratchov 547: if (chroot(wpw_dir) != 0 || chdir("/") != 0)
548: err(1, "cannot chroot to %s", wpw_dir);
549: if (setgroups(1, &wpw_gid) ||
550: setresgid(wpw_gid, wpw_gid, wpw_gid) ||
551: setresuid(wpw_uid, wpw_uid, wpw_uid))
1.18 ratchov 552: err(1, "cannot drop privileges");
553: }
554: for (;;) {
555: if (quit_flag)
556: break;
557: if (!fdpass_peer)
558: break;
559: if (!file_poll())
560: break;
561: }
562: if (fdpass_peer)
563: fdpass_close(fdpass_peer);
564: while (listen_list != NULL)
565: listen_close(listen_list);
566: while (sock_list != NULL)
567: sock_close(sock_list);
568: for (d = dev_list; d != NULL; d = d->next)
569: dev_done(d);
570: for (p = port_list; p != NULL; p = p->next)
571: port_done(p);
572: while (file_poll())
573: ; /* nothing */
574: midi_done();
1.1 ratchov 575: }
576: while (opt_list != NULL)
577: opt_del(opt_list);
578: while (dev_list)
579: dev_del(dev_list);
580: while (port_list)
581: port_del(port_list);
582: filelist_done();
583: unsetsig();
584: return 0;
585: }