Skip to content

Commit c3ef018

Browse files
author
SimulPiscator
committed
sanecpp: protect device handles against concurrency issues
1 parent 5db25cd commit c3ef018

2 files changed

Lines changed: 77 additions & 18 deletions

File tree

sanecpp/sanecpp.cpp

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,30 @@ namespace {
3131
// locale-independent conversions
3232
const std::locale clocale = std::locale("C");
3333

34+
// not all SANE backends are able to handle concurrency,
35+
// so we use our own list of busy devices here.
36+
std::vector<std::pair<std::string, SANE_Handle>> s_busy_devices;
37+
std::mutex s_busy_devices_mutex;
38+
39+
bool is_busy_device(const std::string& name)
40+
{
41+
for (const auto& entry : s_busy_devices) {
42+
if (entry.first == name)
43+
return true;
44+
}
45+
return false;
46+
}
47+
48+
void clear_busy_device(SANE_Handle h)
49+
{
50+
for (auto it = s_busy_devices.begin(); it != s_busy_devices.end(); ++it) {
51+
if (it->second == h) {
52+
s_busy_devices.erase(it);
53+
break;
54+
}
55+
}
56+
}
57+
3458
} // namespace
3559

3660
namespace sanecpp {
@@ -484,6 +508,11 @@ option::unit() const
484508
device_handle
485509
open(const std::string& name, SANE_Status* pStatus)
486510
{
511+
std::unique_lock<std::mutex> lock(s_busy_devices_mutex);
512+
if (is_busy_device(name)) {
513+
*pStatus = SANE_STATUS_DEVICE_BUSY;
514+
return std::shared_ptr<void>();
515+
}
487516
sane_init_addref();
488517
log << "sane_open(" << name << ") -> ";
489518
SANE_Handle h;
@@ -496,7 +525,9 @@ open(const std::string& name, SANE_Status* pStatus)
496525
{
497526
void operator()(SANE_Handle h) const
498527
{
528+
std::unique_lock<std::mutex> lock(s_busy_devices_mutex);
499529
log << "sane_close(" << h << ")" << std::endl;
530+
clear_busy_device(h);
500531
::sane_close(h);
501532
sane_init_release();
502533
}
@@ -539,16 +570,9 @@ enumerate_devices(bool localonly)
539570
}
540571

541572
session::session(const std::string& devicename)
542-
: m_status(SANE_STATUS_GOOD)
543-
{
544-
m_device = sanecpp::open(devicename, &m_status);
545-
init();
546-
}
547-
548-
session::session(device_handle h)
549-
: m_device(h)
550-
, m_status(h ? SANE_STATUS_GOOD : SANE_STATUS_DEVICE_BUSY)
573+
: m_sane_status(SANE_STATUS_GOOD)
551574
{
575+
m_device = sanecpp::open(devicename, &m_sane_status);
552576
init();
553577
}
554578

@@ -564,15 +588,29 @@ session::~session()
564588
session&
565589
session::start()
566590
{
567-
m_status = ::sane_start(m_device.get());
568-
switch (m_status) {
591+
if (m_session_state != pristine) {
592+
log << "session::start(): trying to re-initialize session";
593+
return *this;
594+
}
595+
if (m_sane_status != SANE_STATUS_GOOD) {
596+
log << "session::start(): " << m_sane_status << " at entry" << std::endl;
597+
return *this;
598+
}
599+
600+
m_sane_status = ::sane_start(m_device.get());
601+
switch (m_sane_status) {
569602
case SANE_STATUS_GOOD:
570603
break;
571604
default:
572-
log << "sane_start(" << m_device.get() << "): " << m_status << std::endl;
605+
log << "sane_start(" << m_device.get() << "): " << m_sane_status << std::endl;
606+
}
607+
if (m_sane_status == SANE_STATUS_GOOD)
608+
m_sane_status = ::sane_get_parameters(m_device.get(), &m_parameters);
609+
610+
if (m_sane_status == SANE_STATUS_GOOD) {
611+
m_session_state = initialized;
612+
m_session_state_changed.notify_all();
573613
}
574-
if (m_status == SANE_STATUS_GOOD)
575-
m_status = ::sane_get_parameters(m_device.get(), &m_parameters);
576614
return *this;
577615
}
578616

@@ -582,13 +620,24 @@ session::cancel()
582620
if (m_device) {
583621
log << "sane_cancel(" << m_device.get() << ")" << std::endl;
584622
::sane_cancel(m_device.get());
623+
std::unique_lock<std::mutex> lock(m_session_state_mutex);
624+
while (m_session_state == reading) {
625+
m_session_state_changed.wait(lock);
626+
}
585627
}
586628
return *this;
587629
}
588630

589631
session&
590632
session::read(std::vector<char>& buffer)
591633
{
634+
if (m_session_state != initialized) {
635+
log << "session::read(): trying to read from uninitialized session";
636+
return *this;
637+
}
638+
m_session_state = reading;
639+
m_session_state_changed.notify_all();
640+
592641
SANE_Status status = SANE_STATUS_GOOD;
593642
size_t total = 0;
594643
SANE_Byte* p = reinterpret_cast<SANE_Byte*>(buffer.data());
@@ -604,7 +653,10 @@ session::read(std::vector<char>& buffer)
604653
default:
605654
log << "sane_read(" << m_device.get() << "): " << status << std::endl;
606655
}
607-
m_status = status;
656+
m_sane_status = status;
657+
658+
m_session_state = initialized;
659+
m_session_state_changed.notify_all();
608660
return *this;
609661
}
610662

sanecpp/sanecpp.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2525
#include <sane/sane.h>
2626
#include <string>
2727
#include <vector>
28+
#include <mutex>
29+
#include <condition_variable>
30+
#include <atomic>
2831

2932
namespace sanecpp {
3033

@@ -158,13 +161,12 @@ class session
158161
{
159162
public:
160163
explicit session(const std::string& devicename);
161-
explicit session(device_handle);
162164
~session();
163165

164166
option_set& options() { return m_options; }
165167
const option_set& options() const { return m_options; }
166168

167-
SANE_Status status() const { return m_status; }
169+
SANE_Status status() const { return m_sane_status; }
168170
const SANE_Parameters* parameters() const { return &m_parameters; }
169171

170172
session& start();
@@ -177,8 +179,13 @@ class session
177179

178180
device_handle m_device;
179181
option_set m_options;
180-
SANE_Status m_status;
182+
SANE_Status m_sane_status;
181183
SANE_Parameters m_parameters;
184+
185+
std::mutex m_session_state_mutex;
186+
std::condition_variable m_session_state_changed;
187+
enum { pristine, initialized, reading };
188+
std::atomic<int> m_session_state {pristine};
182189
};
183190

184191
} // namespace sanecpp

0 commit comments

Comments
 (0)