[sane-devel] USB device locking in the snapscan backend
Oliver Schwartz
Oliver.Schwartz at gmx.de
Mon Feb 25 20:23:24 UTC 2008
Hi all,
> The snapscan backend uses a mutex to lock the USB device it's
> working with when sending "atomic" commands.
First off, the original code wasn't written by me and I don't claim I
fully understand it. The background, as far as I understand it, is
this:
The backend may fork a reader process or thread who's only purpose is
to fetch image data from the scanner. It seems that this was invented
to speed up data transfer from the scanner without depending on the
frontend to read the data fast enough. As a result there could be two
processes/threads that access the scanner device at the same time.
This code, as far as I can tell, was in the backend from the very
beginning.
When USB support was added measures were taken to protect access to
the scanner on command level, i.e. only one process/thread can send a
command to the scanner and receive the result. This should allow for
mixed access from both processes/threads when implemented correctly.
It seems, however, that for SCSI no such measures were taken.
To make matters more complicated an USB command queueing was
introduced in the USB part of the backend. The purpose, according to
the comment, was this:
The "Set window" command returns with status "Device busy" when
the scanner is busy. One consequence is that some frontends exits
with an error message if it's started when the scanner is warming
up. A solution was suggested by Dmitri (dmitri at advantrix.com)
The idea is that a SCSI command which returns "device busy" is
stored in a "TODO" queue. The send command function is modified
to first send commands in the queue before the intended command.
So far this strategy has worked flawlessly.
As far as I understand it this code makes the locking necessary in the
first place, since both processes/threads may theoretically add
commands to the queue while the scanner is busy.
Currently there are three implementations for the mutex: One for BeOS,
one for pthread (used on OS/2 and OS-X), one for fork(). The one that
is causing problems is the one for fork() which is used on all Linux
platforms IIRC.
> We're not talking about pthread mutexes here, which is one of the
> available implementations, but IPC semaphores.
>
> Let's look at the code:
>
> static int snapscani_mutex_open(snapscan_mutex_t* sem_id, const
> char* dev) {
> *sem_id = semget( ftok(dev,0x12), 1, IPC_CREAT | 0660 );
> if (*sem_id != -1)
> {
> semop(*sem_id, &sem_signal, 1);
> return 1;
> }
> return 0;
> }
>
> ftok() requires its first argument to be a real, accessible
> file. Obviously, when the code was written, USB scanners were
> driven through the kernel scanner driver and this was not a
> problem.
>
> But today, dev is something like "libusb:001:003" which is all but
> a real, accessible file.
>
> ftok() returns -1 in this case, but this isn't checked for in this
> code, so it happily goes on and semget(0xffffffff, 1, ...).
>
> As it happens, most of the time, that works. Except when another
> similarly careless piece of code has already used 0xffffffff as the
> IPC key.
>
> In which case, all hell breaks loose.
>
> So, 2 bugs in this code:
> - ftok() return value is not checked (BAD BAD BAD)
> - dev is not a real file
>
> Now, two questions:
> - is that locking really useful in the backend?
Honest answer: I don't know for sure. I'd sleep better if I knew that
there was _some_ locking done when there's the remote chance of two
processes writing to the same device.
> - if yes, how do we fix this?
Good question. Maybe the easiest solution is to remove all forking /
threading / locking since it probably won't make a difference on any
machine faster than 100 MHz. At least I never noticed any difference
when testing the backend in single-thread mode. But I'm open to other
suggestions (or even implementations, given my lack of time to
support the backend...)
Regards,
Oliver
More information about the sane-devel
mailing list