On Linux, warn about not being in the audio group, if the audio group exists.
authorbruceperens <bruceperens@01035d8c-6547-0410-b346-abe4f91aad63>
Sun, 9 Mar 2014 07:34:13 +0000 (07:34 +0000)
committerbruceperens <bruceperens@01035d8c-6547-0410-b346-abe4f91aad63>
Sun, 9 Mar 2014 07:34:13 +0000 (07:34 +0000)
git-svn-id: https://svn.code.sf.net/p/freetel/code@1420 01035d8c-6547-0410-b346-abe4f91aad63

freedv-server/source/big_main.cpp
freedv-server/source/drivers.h
freedv-server/source/platform/linux/audio_in_alsa.cpp [new file with mode: 0644]
freedv-server/source/platform/linux/privilege.cpp [new file with mode: 0644]

index f365e4872c706e13fbc7cd42a4d26fe59c2b95f4..50ca23cc2fc245c7a728b3b150979df4508c0448 100644 (file)
@@ -26,6 +26,7 @@ using namespace FreeDV;
 
 static void drivers()
 {
+  check_privileges();
   driver_manager()->print(cout);
 }
 
@@ -191,5 +192,6 @@ main(int argc, char * * argv)
     exit(1);
   }
   set_scheduler();
+  check_privileges();
   return run(&i);
 }
index fc3e37d3fadc63501d825b6caeb9ef3c4e498829..d0d6207350673be9d9efce28d5a67bc0b7fc7b40 100644 (file)
@@ -108,6 +108,9 @@ namespace FreeDV {
   /// has insufficient privilege, this may emit a warning and/or do nothing.
   void         set_scheduler();
 
+  /// Check the user's privileges, and warn if they are inappropriate.
+  void         check_privileges();
+
   /// Virtual base class for all driver classes.
   class Base {
   private:
@@ -702,6 +705,7 @@ namespace FreeDV {
   // program.
   namespace Driver {
     AudioInput *       Tone(const char * parameter);
+    AudioInput *       AudioInALSA(const char * parameter);
     AudioOutput *      AudioSink(const char * parameter);
     AudioOutput *      AudioOutALSA(const char * parameter);
     Codec *            CodecNoOp(const char * parameter);
@@ -717,6 +721,7 @@ namespace FreeDV {
   namespace Enumerator {
     std::ostream &     Tone(std::ostream &);
     std::ostream &     AudioSink(std::ostream &);
+    std::ostream &     AudioInALSA(std::ostream &);
     std::ostream &     AudioOutALSA(std::ostream &);
     std::ostream &     CodecNoOp(std::ostream &);
     std::ostream &     FramerNoOp(std::ostream &);
diff --git a/freedv-server/source/platform/linux/audio_in_alsa.cpp b/freedv-server/source/platform/linux/audio_in_alsa.cpp
new file mode 100644 (file)
index 0000000..808136e
--- /dev/null
@@ -0,0 +1,88 @@
+/// The ALSA audio output driver. 
+
+#include "drivers.h"
+#include <alsa/asoundlib.h>
+#include <sstream>
+#include <stdexcept>
+
+namespace FreeDV {
+  std::ostream & ALSAEnumerate(std::ostream & stream, snd_pcm_stream_t mode);
+
+  /// Audio output "sink", discards the audio, for testing.
+  class AudioInALSA : public AudioInput {
+  private:
+    snd_pcm_t * handle;
+
+  public:
+
+       /// Instantiate the audio sink.
+               AudioInALSA(const char * parameters);
+               ~AudioInALSA();
+
+       /// Return the number of audio samples the device can handle in
+       /// a write without blocking.
+        virtual std::size_t
+               ready();
+
+        /// Read audio into the "short" type.
+       virtual std::size_t
+               read16(std::int16_t * array, std::size_t length);
+  };
+
+  AudioInALSA::AudioInALSA(const char * p)
+  : AudioInput("alsa", p)
+  {
+    const int error = snd_pcm_open(
+     &handle,
+     p,
+     SND_PCM_STREAM_CAPTURE,
+     0);
+
+    if ( error ) {
+      std::ostringstream str;
+
+      str << "Can't open audio device " << p << ": " << snd_strerror(error);
+      throw std::runtime_error(str.str().c_str());
+    }
+  }
+
+  AudioInALSA::~AudioInALSA()
+  {
+  }
+
+  // Read audio into the "short" type.
+  std::size_t
+  AudioInALSA::read16(std::int16_t * array, std::size_t length)
+  {
+    return length;
+  }
+
+  AudioInput *
+  Driver::AudioInALSA(const char * parameter)
+  {
+    return new ::FreeDV::AudioInALSA(parameter);
+  }
+
+  std::ostream &
+  Enumerator::AudioInALSA(std::ostream & stream)
+  {
+    return ALSAEnumerate(stream, SND_PCM_STREAM_CAPTURE);
+  }
+
+  std::size_t
+  AudioInALSA::ready()
+  {
+    return SIZE_MAX;
+  }
+
+  static bool
+  initializer()
+  {
+    driver_manager()->register_audio_input(
+     "alsa",
+     Driver::AudioInALSA,
+     Enumerator::AudioInALSA);
+    return true;
+  }
+  static const bool initialized = initializer();
+}
diff --git a/freedv-server/source/platform/linux/privilege.cpp b/freedv-server/source/platform/linux/privilege.cpp
new file mode 100644 (file)
index 0000000..72c2acf
--- /dev/null
@@ -0,0 +1,50 @@
+#include <iostream>
+#include <unistd.h>
+#include <limits.h>
+#include <grp.h>
+
+static const char insufficient_privilege_message[] =
+"Warning: The user running the program is not a member of the "
+"\"audio\" group.\n"
+"This may make it impossible to access audio devices.\n"
+"To fix, use one of these solutions:\n"
+"\tadd the user to the \"audio\" group.\n"
+"\tadd the setgid-audio privilege to the executable file\n"
+"with these commands, as root:\n"
+"\n"
+"\t\tchgrp audio filename\n"
+"\t\tchmod 2755 filename\n"
+"\n"
+"Alternatively, you can execute this program as root.\n\n";
+
+namespace FreeDV {
+  void
+  check_privileges()
+  {
+    const int uid = getuid();
+    const int euid = geteuid();
+
+    if ( uid == 0 || euid == 0 )
+      return;
+
+    const struct group *       audio = getgrnam("audio");
+    const int                  gid = getgid();
+    const int                  egid = getgid();
+
+    if ( audio ) {
+      gid_t    groups[NGROUPS_MAX];
+      int      length = sizeof(groups) / sizeof(*groups);
+
+      if ( gid == audio->gr_gid || egid == audio->gr_gid )
+        return;
+
+      if ( getgroups(length, groups) == 0 ) {
+        for ( int i = 0; i < length; i++ ) {
+          if ( groups[i] == audio->gr_gid )
+           return;
+        }
+      }
+      std::cerr << insufficient_privilege_message << std::endl;
+    }
+  }
+}