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