On Tue, Sep 17, 2019 at 09:57:28AM +0200, Alexandre Ratchov wrote:
>
> > Regarding the race I was talking about, doesn't dev_reopen() close the
> > current device before trying to open a new one, possibly finding an
> > incompatible device or failing to open it? What happens in this case?
>
> After a SIGHUP, if the new device is incompatible, the old one is
> reopened and sound continues. Now I understand your remark: oops!
> close() and open() are called in the wrong order, so another process
> could steal the device. I'll fix that.
>
Here's a new diff with the fix. Now, the new device is opened before
closing the old one. I've fixed two other bugs in the pure audio
logic.
Index: dev.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.c,v
retrieving revision 1.61
diff -u -p -u -p -r1.61 dev.c
--- dev.c 19 Sep 2019 05:10:19 -0000 1.61
+++ dev.c 20 Sep 2019 07:17:54 -0000
@@ -969,7 +969,8 @@ dev_new(char *path, struct aparams *par,
return NULL;
}
d = xmalloc(sizeof(struct dev));
- d->path = xstrdup(path);
+ d->path_list = NULL;
+ namelist_add(&d->path_list, path);
d->num = dev_sndnum++;
d->opt_list = NULL;
@@ -1174,6 +1175,74 @@ dev_close(struct dev *d)
dev_freebufs(d);
}
+/*
+ * Close the device, but attempt to migrate everything to a new sndio
+ * device.
+ */
+int
+dev_reopen(struct dev *d)
+{
+ struct slot *s;
+ long long pos;
+ unsigned int pstate;
+ int delta;
+
+ /* not opened */
+ if (d->pstate == DEV_CFG)
+ return 1;
+
+ /* save state */
+ delta = d->delta;
+ pstate = d->pstate;
+
+ if (!dev_sio_reopen(d))
+ return 0;
+
+ /* reopen returns a stopped device */
+ d->pstate = DEV_INIT;
+
+ /* reallocate new buffers, with new parameters */
+ dev_freebufs(d);
+ dev_allocbufs(d);
+
+ /*
+ * adjust time positions, make anything go back delta ticks, so
+ * that the new device can start at zero
+ */
+ for (s = d->slot_list; s != NULL; s = s->next) {
+ pos = (long long)s->delta * d->round + s->delta_rem;
+ pos -= (long long)delta * s->round;
+ s->delta_rem = pos % (int)d->round;
+ s->delta = pos / (int)d->round;
+ if (log_level >= 3) {
+ slot_log(s);
+ log_puts(": adjusted: delta -> ");
+ log_puti(s->delta);
+ log_puts(", delta_rem -> ");
+ log_puti(s->delta_rem);
+ log_puts("\n");
+ }
+
+ /* reinitilize the format conversion chain */
+ slot_initconv(s);
+ }
+ if (d->tstate == MMC_RUN) {
+ d->mtc.delta -= delta * MTC_SEC;
+ if (log_level >= 2) {
+ dev_log(d);
+ log_puts(": adjusted mtc: delta ->");
+ log_puti(d->mtc.delta);
+ log_puts("\n");
+ }
+ }
+
+ /* start the device if needed */
+ if (pstate == DEV_RUN)
+ dev_wakeup(d);
+
+ return 1;
+}
+
int
dev_ref(struct dev *d)
{
@@ -1280,7 +1349,7 @@ dev_del(struct dev *d)
}
midi_del(d->midi);
*p = d->next;
- xfree(d->path);
+ namelist_clear(&d->path_list);
xfree(d);
}
Index: dev.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/dev.h,v
retrieving revision 1.21
diff -u -p -u -p -r1.21 dev.h
--- dev.h 12 Jul 2019 06:30:55 -0000 1.21
+++ dev.h 20 Sep 2019 07:17:54 -0000
@@ -159,7 +159,7 @@ struct dev {
#define DEV_INIT 1 /* stopped */
#define DEV_RUN 2 /* playin & recording */
unsigned int pstate; /* one of above */
- char *path; /* sio path */
+ struct name *path_list;
/*
* actual parameters and runtime state (i.e. once opened)
@@ -201,6 +201,7 @@ extern struct dev *dev_list;
void dev_log(struct dev *);
void dev_close(struct dev *);
+int dev_reopen(struct dev *);
struct dev *dev_new(char *, struct aparams *, unsigned int, unsigned int,
unsigned int, unsigned int, unsigned int, unsigned int);
struct dev *dev_bynum(int);
Index: fdpass.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/fdpass.c,v
retrieving revision 1.6
diff -u -p -u -p -r1.6 fdpass.c
--- fdpass.c 28 Jun 2019 13:35:03 -0000 1.6
+++ fdpass.c 20 Sep 2019 07:17:54 -0000
@@ -300,6 +300,7 @@ fdpass_in_helper(void *arg)
struct fdpass *f = arg;
struct dev *d;
struct port *p;
+ struct name *path;
if (!fdpass_recv(f, &cmd, &num, &mode, &fd))
return;
@@ -314,7 +315,11 @@ fdpass_in_helper(void *arg)
fdpass_close(f);
return;
}
- fd = sio_sun_getfd(d->path, mode, 1);
+ for (path = d->path_list; path != NULL; path = path->next) {
+ fd = sio_sun_getfd(path->str, mode, 1);
+ if (fd != -1)
+ break;
+ }
break;
case FDPASS_OPEN_MIDI:
p = port_bynum(num);
@@ -326,7 +331,11 @@ fdpass_in_helper(void *arg)
fdpass_close(f);
return;
}
- fd = mio_rmidi_getfd(p->path, mode, 1);
+ for (path = p->path_list; path != NULL; path = path->next) {
+ fd = mio_rmidi_getfd(path->str, mode, 1);
+ if (fd != -1)
+ break;
+ }
break;
default:
fdpass_close(f);
Index: midi.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/midi.c,v
retrieving revision 1.21
diff -u -p -u -p -r1.21 midi.c
--- midi.c 29 Aug 2019 07:19:15 -0000 1.21
+++ midi.c 20 Sep 2019 07:17:54 -0000
@@ -438,7 +438,8 @@ port_new(char *path, unsigned int mode,
struct port *c;
c = xmalloc(sizeof(struct port));
- c->path = xstrdup(path);
+ c->path_list = NULL;
+ namelist_add(&c->path_list, path);
c->state = PORT_CFG;
c->hold = hold;
c->midi = midi_new(&port_midiops, c, mode);
@@ -468,7 +469,7 @@ port_del(struct port *c)
#endif
}
*p = c->next;
- xfree(c->path);
+ namelist_clear(&c->path_list);
xfree(c);
}
@@ -592,4 +593,16 @@ port_done(struct port *c)
{
if (c->state == PORT_INIT)
port_drain(c);
+}
+
+int
+port_reopen(struct port *p)
+{
+ if (p->state == PORT_CFG)
+ return 1;
+
+ if (!port_mio_reopen(p))
+ return 0;
+
+ return 1;
}
Index: midi.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/midi.h,v
retrieving revision 1.9
diff -u -p -u -p -r1.9 midi.h
--- midi.h 29 Aug 2019 07:11:28 -0000 1.9
+++ midi.h 20 Sep 2019 07:17:54 -0000
@@ -88,7 +88,7 @@ struct port {
#define PORT_DRAIN 2
unsigned int state;
unsigned int num; /* port serial number */
- char *path;
+ struct name *path_list;
int hold; /* hold the port open ? */
struct midi *midi;
};
@@ -121,5 +121,6 @@ int port_init(struct port *);
void port_done(struct port *);
void port_drain(struct port *);
int port_close(struct port *);
+int port_reopen(struct port *);
#endif /* !defined(MIDI_H) */
Index: miofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/miofile.c,v
retrieving revision 1.4
diff -u -p -u -p -r1.4 miofile.c
--- miofile.c 20 Dec 2015 11:38:33 -0000 1.4
+++ miofile.c 20 Sep 2019 07:17:54 -0000
@@ -51,7 +51,34 @@ port_mio_open(struct port *p)
p->mio.hdl = fdpass_mio_open(p->num, p->midi->mode);
if (p->mio.hdl == NULL)
return 0;
- p->mio.file = file_new(&port_mio_ops, p, p->path, mio_nfds(p->mio.hdl));
+ p->mio.file = file_new(&port_mio_ops, p, "port", mio_nfds(p->mio.hdl));
+ return 1;
+}
+
+/*
+ * Open an alternate port. Upon success, close the old port
+ * and continue using the new one.
+ */
+int
+port_mio_reopen(struct port *p)
+{
+ struct mio_hdl *hdl;
+
+ hdl = fdpass_mio_open(p->num, p->midi->mode);
+ if (hdl == NULL) {
+ if (log_level >= 1) {
+ port_log(p);
+ log_puts(": couldn't open an alternate port\n");
+ }
+ return 0;
+ }
+
+ /* close unused device */
+ file_del(p->mio.file);
+ mio_close(p->mio.hdl);
+
+ p->mio.hdl = hdl;
+ p->mio.file = file_new(&port_mio_ops, p, "port", mio_nfds(hdl));
return 1;
}
@@ -129,5 +156,6 @@ port_mio_hup(void *arg)
{
struct port *p = arg;
- port_close(p);
+ if (!port_reopen(p))
+ port_close(p);
}
Index: miofile.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/miofile.h,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 miofile.h
--- miofile.h 23 Nov 2012 07:03:28 -0000 1.1
+++ miofile.h 20 Sep 2019 07:17:54 -0000
@@ -25,6 +25,7 @@ struct port_mio {
};
int port_mio_open(struct port *);
+int port_mio_reopen(struct port *);
void port_mio_close(struct port *);
#endif /* !defined(MIOFILE_H) */
Index: siofile.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/siofile.c,v
retrieving revision 1.15
diff -u -p -u -p -r1.15 siofile.c
--- siofile.c 29 Aug 2019 07:05:47 -0000 1.15
+++ siofile.c 20 Sep 2019 07:17:54 -0000
@@ -212,7 +212,7 @@ dev_sio_open(struct dev *d)
if (!(mode & MODE_REC))
d->mode &= ~MODE_REC;
sio_onmove(d->sio.hdl, dev_sio_onmove, d);
- d->sio.file = file_new(&dev_sio_ops, d, d->path, sio_nfds(d->sio.hdl));
+ d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(d->sio.hdl));
timo_set(&d->sio.watchdog, dev_sio_timeout, d);
return 1;
bad_close:
@@ -220,6 +220,79 @@ dev_sio_open(struct dev *d)
return 0;
}
+/*
+ * Open an alternate device. Upon success and if the new device is
+ * compatible with the old one, close the old device and continue
+ * using the new one. The new device is not started.
+ */
+int
+dev_sio_reopen(struct dev *d)
+{
+ struct sio_par par;
+ struct sio_hdl *hdl;
+
+ hdl = fdpass_sio_open(d->num, d->mode & (MODE_PLAY | MODE_REC));
+ if (hdl == NULL) {
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": couldn't open an alternate device\n");
+ }
+ return 0;
+ }
+
+ sio_initpar(&par);
+ par.bits = d->par.bits;
+ par.bps = d->par.bps;
+ par.sig = d->par.sig;
+ par.le = d->par.le;
+ par.msb = d->par.msb;
+ if (d->mode & SIO_PLAY)
+ par.pchan = d->pchan;
+ if (d->mode & SIO_REC)
+ par.rchan = d->rchan;
+ par.appbufsz = d->bufsz;
+ par.round = d->round;
+ par.rate = d->rate;
+ if (!sio_setpar(hdl, &par))
+ goto bad_close;
+ if (!sio_getpar(hdl, &par))
+ goto bad_close;
+
+ /* check if new parameters are compatible with old ones */
+ if (par.round != d->round || par.bufsz != d->bufsz ||
+ par.rate != d->rate) {
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": alternate device not compatible\n");
+ }
+ goto bad_close;
+ }
+
+ /* close unused device */
+ timo_del(&d->sio.watchdog);
+ file_del(d->sio.file);
+ sio_close(d->sio.hdl);
+
+ /* update parameters */
+ d->par.bits = par.bits;
+ d->par.bps = par.bps;
+ d->par.sig = par.sig;
+ d->par.le = par.le;
+ d->par.msb = par.msb;
+ if (d->mode & SIO_PLAY)
+ d->pchan = par.pchan;
+ if (d->mode & SIO_REC)
+ d->rchan = par.rchan;
+
+ d->sio.hdl = hdl;
+ d->sio.file = file_new(&dev_sio_ops, d, "dev", sio_nfds(hdl));
+ sio_onmove(hdl, dev_sio_onmove, d);
+ return 1;
+bad_close:
+ sio_close(hdl);
+ return 0;
+}
+
void
dev_sio_close(struct dev *d)
{
@@ -494,5 +567,6 @@ dev_sio_hup(void *arg)
log_puts(": disconnected\n");
}
#endif
- dev_close(d);
+ if (!dev_reopen(d))
+ dev_close(d);
}
Index: siofile.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/siofile.h,v
retrieving revision 1.3
diff -u -p -u -p -r1.3 siofile.h
--- siofile.h 28 Sep 2013 18:49:32 -0000 1.3
+++ siofile.h 20 Sep 2019 07:17:54 -0000
@@ -38,6 +38,7 @@ struct dev_sio {
};
int dev_sio_open(struct dev *);
+int dev_sio_reopen(struct dev *);
void dev_sio_close(struct dev *);
void dev_sio_log(struct dev *);
void dev_sio_start(struct dev *);
Index: sndiod.8
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.8,v
retrieving revision 1.2
diff -u -p -u -p -r1.2 sndiod.8
--- sndiod.8 18 Jan 2016 11:38:07 -0000 1.2
+++ sndiod.8 20 Sep 2019 07:17:55 -0000
@@ -29,10 +29,12 @@
.Op Fl C Ar min : Ns Ar max
.Op Fl c Ar min : Ns Ar max
.Op Fl e Ar enc
+.Op Fl F Ar device
.Op Fl f Ar device
.Op Fl j Ar flag
.Op Fl L Ar addr
.Op Fl m Ar mode
+.Op Fl Q Ar port
.Op Fl q Ar port
.Op Fl r Ar rate
.Op Fl s Ar name
@@ -182,6 +184,18 @@ or
Only the signedness and the precision are mandatory.
Examples:
.Va u8 , s16le , s24le3 , s24le4lsb .
+.It Fl F Ar device
+Specify an alternate device to use.
+If doesn't work, the one given with the last
+.Fl f
+or
+.Fl F
+options will be used.
+For instance, specifying a USB device following a
+PCI device allows
+.Nm
+to use the USB one preferably when it's connected
+and to fall back to the PCI one when it's disconnected.
.It Fl f Ar device
Add this
.Xr sndio 7
@@ -245,6 +259,15 @@ but the same sub-device cannot be used f
The default is
.Ar play , Ns Ar rec
(i.e. full-duplex).
+.It Fl Q Ar port
+Specify an alternate MIDI port to use.
+If doesn't work, the one given with the last
+.Fl Q
+or
+.Fl q
+options will be used.
+For instance, this allows to replace a USB MIDI controller without
+the need to restart programs using it.
.It Fl q Ar port
Expose the given MIDI port.
This allows multiple programs to share the port.
@@ -376,11 +399,15 @@ is
If
.Nm
is sent
-.Dv SIGHUP ,
.Dv SIGINT
or
.Dv SIGTERM ,
it terminates.
+If
+.Nm
+is sent
+.Dv SIGHUP ,
+it reopens all audio devices and MIDI ports.
.Pp
By default, when the program cannot accept
recorded data fast enough or cannot provide data to play fast enough,
Index: sndiod.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/sndiod.c,v
retrieving revision 1.35
diff -u -p -u -p -r1.35 sndiod.c
--- sndiod.c 29 Jun 2019 21:23:18 -0000 1.35
+++ sndiod.c 20 Sep 2019 07:17:55 -0000
@@ -75,7 +75,7 @@
* block size if neither ``-z'' nor ``-b'' is used
*/
#ifndef DEFAULT_ROUND
-#define DEFAULT_ROUND 960
+#define DEFAULT_ROUND 480
#endif
/*
@@ -93,6 +93,7 @@
#endif
void sigint(int);
+void sighup(int);
void opt_ch(int *, int *);
void opt_enc(struct aparams *);
int opt_mmc(void);
@@ -109,12 +110,13 @@ struct opt *mkopt(char *, struct dev *,
int, int, int, int, int, int, int, int);
unsigned int log_level = 0;
-volatile sig_atomic_t quit_flag = 0;
+volatile sig_atomic_t quit_flag = 0, reopen_flag = 0;
char usagestr[] = "usage: sndiod [-d] [-a flag] [-b nframes] "
- "[-C min:max] [-c min:max] [-e enc]\n\t"
- "[-f device] [-j flag] [-L addr] [-m mode] [-q port] [-r rate]\n\t"
- "[-s name] [-t mode] [-U unit] [-v volume] [-w flag] [-z nframes]\n";
+ "[-C min:max] [-c min:max]\n\t"
+ "[-e enc] [-F device] [-f device] [-j flag] [-L addr] [-m mode]\n\t"
+ "[-Q port] [-q port] [-r rate] [-s name] [-t mode] [-U unit]\n\t"
+ "[-v volume] [-w flag] [-z nframes]\n";
/*
* SIGINT handler, it raises the quit flag. If the flag is already set,
@@ -129,6 +131,16 @@ sigint(int s)
quit_flag = 1;
}
+/*
+ * SIGHUP handler, it raises the reopen flag, which requests devices
+ * to be reopened.
+ */
+void
+sighup(int s)
+{
+ reopen_flag = 1;
+}
+
void
opt_ch(int *rcmin, int *rcmax)
{
@@ -231,6 +243,7 @@ setsig(void)
struct sigaction sa;
quit_flag = 0;
+ reopen_flag = 0;
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = sigint;
@@ -238,6 +251,7 @@ setsig(void)
err(1, "sigaction(int) failed");
if (sigaction(SIGTERM, &sa, NULL) == -1)
err(1, "sigaction(term) failed");
+ sa.sa_handler = sighup;
if (sigaction(SIGHUP, &sa, NULL) == -1)
err(1, "sigaction(hup) failed");
}
@@ -294,7 +308,8 @@ mkdev(char *path, struct aparams *par,
struct dev *d;
for (d = dev_list; d != NULL; d = d->next) {
- if (strcmp(d->path, path) == 0)
+ if (d->path_list->next == NULL &&
+ strcmp(d->path_list->str, path) == 0)
return d;
}
if (!bufsz && !round) {
@@ -316,7 +331,8 @@ mkport(char *path, int hold)
struct port *c;
for (c = port_list; c != NULL; c = c->next) {
- if (strcmp(c->path, path) == 0)
+ if (c->path_list->next == NULL &&
+ strcmp(c->path_list->str, path) == 0)
return c;
}
c = port_new(path, MODE_MIDIMASK, hold);
@@ -361,6 +377,7 @@ start_helper(int background)
struct dev *d;
struct port *p;
struct passwd *pw;
+ struct name *n;
int s[2];
pid_t pid;
@@ -395,10 +412,14 @@ start_helper(int background)
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
err(1, "cannot drop privileges");
}
- for (d = dev_list; d != NULL; d = d->next)
- dounveil(d->path, "rsnd/", "/dev/audio");
- for (p = port_list; p != NULL; p = p->next)
- dounveil(p->path, "rmidi/", "/dev/rmidi");
+ for (d = dev_list; d != NULL; d = d->next) {
+ for (n = d->path_list; n != NULL; n = n->next)
+ dounveil(n->str, "rsnd/", "/dev/audio");
+ }
+ for (p = port_list; p != NULL; p = p->next) {
+ for (n = p->path_list; n != NULL; n = n->next)
+ dounveil(n->str, "rmidi/", "/dev/rmidi");
+ }
if (pledge("stdio sendfd rpath wpath", NULL) == -1)
err(1, "pledge");
while (file_poll())
@@ -461,7 +482,8 @@ main(int argc, char **argv)
mode = MODE_PLAY | MODE_REC;
tcpaddr_list = NULL;
- while ((c = getopt(argc, argv,
"a:b:c:C:de:f:j:L:m:q:r:s:t:U:v:w:x:z:")) != -1) {
+ while ((c = getopt(argc, argv,
+ "a:b:c:C:de:F:f:j:L:m:Q:q:r:s:t:U:v:w:x:z:")) != -1) {
switch (c) {
case 'd':
log_level++;
@@ -518,6 +540,11 @@ main(int argc, char **argv)
case 'q':
mkport(optarg, hold);
break;
+ case 'Q':
+ if (port_list == NULL)
+ errx(1, "-Q %s: no ports defined", optarg);
+ namelist_add(&port_list->path_list, optarg);
+ break;
case 'a':
hold = opt_onoff();
break;
@@ -538,6 +565,11 @@ main(int argc, char **argv)
mkdev(optarg, &par, 0, bufsz, round,
rate, hold, autovol);
break;
+ case 'F':
+ if (dev_list == NULL)
+ errx(1, "-F %s: no devices defined", optarg);
+ namelist_add(&dev_list->path_list, optarg);
+ break;
default:
fputs(usagestr, stderr);
return 1;
@@ -617,6 +649,13 @@ main(int argc, char **argv)
for (;;) {
if (quit_flag)
break;
+ if (reopen_flag) {
+ reopen_flag = 0;
+ for (d = dev_list; d != NULL; d = d->next)
+ dev_reopen(d);
+ for (p = port_list; p != NULL; p = p->next)
+ port_reopen(p);
+ }
if (!fdpass_peer)
break;
if (!file_poll())
Index: utils.c
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/utils.c,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 utils.c
--- utils.c 5 Jul 2019 07:34:40 -0000 1.5
+++ utils.c 20 Sep 2019 07:17:55 -0000
@@ -188,3 +188,30 @@ xstrdup(char *s)
memcpy(p, s, size);
return p;
}
+
+/*
+ * copy and append the given string to the name list
+ */
+void
+namelist_add(struct name **list, char *str)
+{
+ struct name *n;
+ size_t size;
+
+ size = strlen(str) + 1;
+ n = xmalloc(sizeof(struct name) + size);
+ memcpy(n->str, str, size);
+ n->next = *list;
+ *list = n;
+}
+
+void
+namelist_clear(struct name **list)
+{
+ struct name *n;
+
+ while ((n = *list) != NULL) {
+ *list = n->next;
+ xfree(n);
+ }
+}
Index: utils.h
===================================================================
RCS file: /cvs/src/usr.bin/sndiod/utils.h,v
retrieving revision 1.3
diff -u -p -u -p -r1.3 utils.h
--- utils.h 12 May 2013 04:58:41 -0000 1.3
+++ utils.h 20 Sep 2019 07:17:55 -0000
@@ -20,6 +20,11 @@
#include <stddef.h>
+struct name {
+ struct name *next;
+ char str[];
+};
+
void log_puts(char *);
void log_putx(unsigned long);
void log_putu(unsigned long);
@@ -30,6 +35,9 @@ void log_flush(void);
void *xmalloc(size_t);
char *xstrdup(char *);
void xfree(void *);
+
+void namelist_add(struct name **, char *);
+void namelist_clear(struct name **);
/*
* Log levels: