On Fri, Dec 09, 2022 at 12:10:59PM +0100, Alexandre Ratchov wrote:
> This diff adds an option to display variables periodically. Basically
> it replaces this usage:
> 
>       while sleep 1; do audioctl play.errors; done
> 
> by
> 
>       audioctl -w 1 play.errors
> 
> The purpose of above audioctl commands is to debug underruns, so we
> don't want to fork a new process and reopen the device. This would
> trigger longer kernel code-paths and may cause additional underruns
> than the ones being investigated.
> 
> OK?

I like the idea, but I would like to tweak some things.

- Add [-w wait] to the first synoptic form in the manpage.  It's legal
  to do e.g.

        # audioctl -w 1

- Call the variable "wait" in audioctl.c to match the manpage.

- Update usagestr to mention [-w wait].

- When polling variables periodically, it's better to use setitimer(2)
  and sigsuspend(2) instead of sleep(3).  setitimer(2) keeps the period
  from drifting.

- Let the user SIGINT (^C) out of the program without returning an
  error to the shell.

  I'm unsure about this one, but it seems logical to give the user a
  way to gracefully terminate the program.  You say in the manpage that
  the program will continue printing until it is interrupted.

Index: audioctl.8
===================================================================
RCS file: /cvs/src/usr.bin/audioctl/audioctl.8,v
retrieving revision 1.4
diff -u -p -r1.4 audioctl.8
--- audioctl.8  23 Apr 2020 00:16:59 -0000      1.4
+++ audioctl.8  9 Dec 2022 18:41:45 -0000
@@ -35,8 +35,10 @@
 .Sh SYNOPSIS
 .Nm audioctl
 .Op Fl f Ar file
+.Op Fl w wait
 .Nm audioctl
 .Op Fl n
+.Op Fl w wait
 .Op Fl f Ar file
 .Ar name ...
 .Nm audioctl
@@ -59,6 +61,12 @@ The default is
 Suppress printing of the variable name.
 .It Fl q
 Suppress all output when setting a variable.
+.It Fl w Ar wait
+Pause
+.Ar wait
+seconds between each display.
+.Nm
+will display variables until it is interrupted.
 .It Ar name Ns = Ns Ar value
 Attempt to set the specified variable
 .Ar name
@@ -130,10 +138,10 @@ audio control devices
 audio devices
 .El
 .Sh EXAMPLES
-Display the number of bytes of silence inserted during play buffer
-underruns since device started:
+Once per second, display the number of bytes of silence inserted
+during play buffer underruns since device started:
 .Bd -literal -offset indent
-# audioctl play.errors
+# audioctl -w 1 play.errors
 .Ed
 .Pp
 Use signed 24-bit samples and 44100Hz sample rate:
Index: audioctl.c
===================================================================
RCS file: /cvs/src/usr.bin/audioctl/audioctl.c,v
retrieving revision 1.43
diff -u -p -r1.43 audioctl.c
--- audioctl.c  12 Jul 2021 15:09:19 -0000      1.43
+++ audioctl.c  9 Dec 2022 18:41:45 -0000
@@ -16,9 +16,11 @@
  */
 #include <sys/types.h>
 #include <sys/ioctl.h>
+#include <sys/time.h>
 #include <sys/audioio.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -43,6 +45,7 @@ struct field {
 #define STR    2
 #define ENC    3
        int type;
+       int show;
        int set;
 } fields[] = {
        {"name",                &rname.name,            NULL,           STR},
@@ -63,11 +66,24 @@ struct field {
 };
 
 const char usagestr[] =
-       "usage: audioctl [-f file]\n"
-       "       audioctl [-n] [-f file] name ...\n"
+       "usage: audioctl [-f file] [-w wait]\n"
+       "       audioctl [-n] [-f file] [-w wait] name ...\n"
        "       audioctl [-nq] [-f file] name=value ...\n";
 
 int fd, show_names = 1, quiet = 0;
+unsigned int wait;
+volatile sig_atomic_t uninterrupted = 1;
+
+void
+handle_alrm(int signo)
+{
+}
+
+void
+handle_int(int signo)
+{
+       uninterrupted = 0;
+}
 
 /*
  * parse encoding string (examples: s8, u8, s16, s16le, s24be ...)
@@ -194,24 +210,15 @@ parse_field(struct field *f, void *addr,
 void
 audio_main(int argc, char **argv)
 {
+       struct itimerval itv;
        struct field *f;
        char *lhs, *rhs;
+       sigset_t empty;
        int set = 0;
 
-       if (ioctl(fd, AUDIO_GETSTATUS, &rstatus) == -1)
-               err(1, "AUDIO_GETSTATUS");
-       if (ioctl(fd, AUDIO_GETDEV, &rname) == -1)
-               err(1, "AUDIO_GETDEV");
-       if (ioctl(fd, AUDIO_GETPAR, &rpar) == -1)
-               err(1, "AUDIO_GETPAR");
-       if (ioctl(fd, AUDIO_GETPOS, &rpos) == -1)
-               err(1, "AUDIO_GETPOS");
        if (argc == 0) {
-               for (f = fields; f->name != NULL; f++) {
-                       printf("%s=", f->name);
-                       print_field(f, f->raddr);
-                       printf("\n");
-               }
+               for (f = fields; f->name != NULL; f++)
+                       f->show = 1;
        }
        AUDIO_INITPAR(&wpar);
        for (; argc > 0; argc--, argv++) {
@@ -231,15 +238,51 @@ audio_main(int argc, char **argv)
                        parse_field(f, f->waddr, rhs);
                        f->set = 1;
                        set = 1;
-               } else {
+               } else
+                       f->show = 1;
+       }
+
+       if (set && wait)
+               errx(1, "the -w flag is incompatible with variable assignment");
+
+       if (wait != 0) {
+               if (signal(SIGALRM, handle_alrm) == SIG_ERR)
+                       err(1, "signal");
+               if (signal(SIGINT, handle_int) == SIG_ERR)
+                       err(1, "signal");
+               sigemptyset(&empty);
+               itv.it_value.tv_sec = wait;
+               itv.it_value.tv_usec = 0;
+               itv.it_interval = itv.it_value;
+               if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
+                       err(1, "setitimer");
+       }
+
+       while (uninterrupted) {
+               if (ioctl(fd, AUDIO_GETSTATUS, &rstatus) == -1)
+                       err(1, "AUDIO_GETSTATUS");
+               if (ioctl(fd, AUDIO_GETDEV, &rname) == -1)
+                       err(1, "AUDIO_GETDEV");
+               if (ioctl(fd, AUDIO_GETPAR, &rpar) == -1)
+                       err(1, "AUDIO_GETPAR");
+               if (ioctl(fd, AUDIO_GETPOS, &rpos) == -1)
+                       err(1, "AUDIO_GETPOS");
+               for (f = fields; f->name != NULL; f++) {
+                       if (!f->show)
+                               continue;
                        if (show_names)
                                printf("%s=", f->name);
                        print_field(f, f->raddr);
                        printf("\n");
                }
+               if (wait == 0)
+                       break;
+               sigsuspend(&empty);
        }
+
        if (!set)
                return;
+
        if (ioctl(fd, AUDIO_SETPAR, &wpar) == -1)
                err(1, "AUDIO_SETPAR");
        if (ioctl(fd, AUDIO_GETPAR, &wpar) == -1)
@@ -261,9 +304,10 @@ int
 main(int argc, char **argv)
 {
        char *path = "/dev/audioctl0";
+       const char *errstr;
        int c;
 
-       while ((c = getopt(argc, argv, "anf:q")) != -1) {
+       while ((c = getopt(argc, argv, "anf:qw:")) != -1) {
                switch (c) {
                case 'a':       /* ignored, compat */
                        break;
@@ -275,6 +319,11 @@ main(int argc, char **argv)
                        break;
                case 'q':
                        quiet = 1;
+                       break;
+               case 'w':
+                       wait = strtonum(optarg, 1, UINT_MAX, &errstr);
+                       if (errstr != NULL)
+                               errx(1, "wait is %s: %s", errstr, optarg);
                        break;
                default:
                        fputs(usagestr, stderr);

Reply via email to