295 lines
13 KiB

Delicate Device Locking Protocol
(a joint sub project of cdrkit and libburnia)
(contact: )
Our projects provide programs which allow recording of data on CD or DVD.
We encounter an increasing number of bug reports about spoiled burn runs and
wasted media which obviously have one common cause: interference by other
programs which access the drive's device files.
There is some riddling about which gestures exactly are dangerous for
ongoing recordings or can cause weirdly misformatted drive replies to MMC
We do know, nevertheless, that these effects do not occur if no other program
accesses a device file of the drive while our programs use it.
DDLP shall help to avoid collisions between programs in the process of
recording to a CD or DVD drive and other programs which access that drive.
The protocol intends to provide advisory locking. So any good-willing program
has to take some extra precautions to participate.
If a program does not feel vulnerable to disturbance, then the precautions
impose much less effort than if the program feels the need for protection.
Two locking strategies are specified:
DDLP-A operates on device files only. It is very Linux specific.
DDLP-B adds proxy lock files, inspired by FHS /var/lock standard.
This protocol relies on the hardly documented feature open(O_EXCL | O_RDWR)
with Linux device files and on POSIX compliant fcntl(F_SETLK).
Other than the original meaning of O_EXCL with creating regular files, the
effect on device files is mutual exclusion of access. I.e. if one
filedescriptor is open on that combination of major-minor device number, then
no other open(O_EXCL) will succeed. But open() without O_EXCL would succeed.
So this is advisory and exclusive locking.
With kernel 2.6 it seems to work on all device drivers which might get used
to access a CD/DVD drive.
The vulnerable programs shall not start their operation before they occupied a
wide collection of drive representations.
Non-vulnerable programs shall take care to detect the occupation of _one_ such
So for Friendly Programs
A program which does not feel vulnerable to disturbance is urged to access
CD/DVD drives by opening a file descriptor which will uphold the lock
as long as it does not get closed. There are two alternative ways to achieve
Very reliable is
open( some_path , O_EXCL | ...)
But O_EXCL imposes restrictions and interferences:
- O_EXCL | O_RDONLY does not succeed with /dev/sg* !
- O_EXCL cannot provide shared locks for programs which only want to lock
against burn programs but not against their own peers.
- O_EXCL keeps from obtaining information by harmless activities.
- O_EXCL already has a meaning with devices which are mounted as filesystems.
This priority meaning is more liberal than the one needed for CD/DV recording
So it may be necessary to use a cautious open() without O_EXCL and to aquire
a POSIX lock via fcntl(). "Cautious" means to add O_NDELAY to the flags of
open(), because this is declared to avoid side effects within open().
With this gesture it is important to use the paths expected by our burn
programs: /dev/sr[0..255] /dev/scd[0..255] /dev/sg[0..255] /dev/hd[a..z]
because fcntl(F_SETLK) does not lock the device but only a device-inode.
std_path = one of the standard device files:
/dev/sr[0..255] /dev/scd[0..255] /dev/sg[0..255] /dev/hd[a..z]
or a symbolic link pointing to one of them.
open( std_path , ... | O_NDELAY)
fcntl(F_SETLK) and close() on failure
... eventually disable O_NDELAY by fcntl(F_SETFL) ...
There is a pitfall mentioned in man 2 fcntl :
"locks are automatically released [...] if it closes any file descriptor
referring to a file on which locks are held. This is bad [...]"
So you may have to re-lock after some temporary fd got closed.
Vulnerable Programs
For programs which do feel vulnerable, O_EXCL would suffice for the /dev/hd*
device file family and their driver. But USB and SATA recorders appear with
at least two different major-minor combinations simultaneously.
One as /dev/sr* alias /dev/scd*, the other as /dev/sg*.
The same is true for ide-scsi or recorders attached to SCSI controllers.
So, in order to lock any access to the recorder, one has to open(O_EXCL)
not only the device file that is intended for accessing the recorder but also
a device file of any other major-minor representation of the recorder.
This is done via the SCSI address parameter vector (Host,Channel,Id,Lun)
and a search on standard device file paths /dev/sr* /dev/scd* /dev/sg*.
In this text the alternative device representations are called "siblings".
For finding them, it is necessary to apply open() to many device files which
might be occupied by delicate operations. On the other hand it is very
important to occupy all reasonable representations of the drive.
So the reading of the (Host,Channel,Id,Lun) parameters demands an
open(O_RDONLY | O_NDELAY) _without_ fcntl() in order to find the outmost
number of representations among the standard device files. Only ioctls
Hopefully this gesture is unable to cause harmful side effects on kernel 2.6.
At least one file of each class sr, scd and sg should be found to regard
the occupation as satisfying. Thus corresponding sr-scd-sg triplets should have
matching ownerships and access permissions.
One will have to help the sysadmins to find those triplets.
A spicy detail is that sr and scd may be distinct device files for the same
major-minor combination. In this case fcntl() locks on both are needed
but O_EXCL can only be applied to one of them.
An open and free implementation ddlpa.[ch] is provided as
The current version of this text is
Put ddlpa.h and ddlpa.c into the same directory and compile as test program by
cc -g -Wall -DDDLPA_C_STANDALONE -o ddlpa ddlpa.c
Use it to occupy a drive's representations for a given number of seconds
./ddlpa /dev/sr0 300
It should do no harm to any of your running activities.
If it does: Please, please alert us.
Your own programs should not be able to circumvent the occupation if they
obey above rules for Friendly Programs.
Of course ./ddlpa should be unable to circumvent itself.
A successfull occupation looks like
DDLPA_DEBUG: ddlpa_std_by_rdev("/dev/scd0") = "/dev/sr0"
DDLPA_DEBUG: ddlpa_collect_siblings() found "/dev/sr0"
DDLPA_DEBUG: ddlpa_collect_siblings() found "/dev/scd0"
DDLPA_DEBUG: ddlpa_collect_siblings() found "/dev/sg0"
DDLPA_DEBUG: ddlpa_occupy() : '/dev/scd0'
DDLPA_DEBUG: ddlpa_occupy() O_EXCL : '/dev/sg0'
DDLPA_DEBUG: ddlpa_occupy() O_EXCL : '/dev/sr0'
---------------------------------------------- Lock gained
ddlpa: opened /dev/sr0
ddlpa: opened siblings: /dev/scd0 /dev/sg0
slept 1 seconds of 300
Now an attempt via device file alias /dev/NEC must fail:
DDLPA_DEBUG: ddlpa_std_by_rdev("/dev/NEC") = "/dev/sg0"
DDLPA_DEBUG: ddlpa_collect_siblings() found "/dev/sr0"
DDLPA_DEBUG: ddlpa_collect_siblings() found "/dev/scd0"
DDLPA_DEBUG: ddlpa_collect_siblings() found "/dev/sg0"
Cannot exclusively open '/dev/sg0'
Reason given : Failed to open O_RDWR | O_NDELAY | O_EXCL : '/dev/sr0'
Error condition : 16 'Device or resource busy'
With hdc, of course, things are trivial
DDLPA_DEBUG: ddlpa_std_by_rdev("/dev/hdc") = "/dev/hdc"
DDLPA_DEBUG: ddlpa_occupy() O_EXCL : '/dev/hdc'
---------------------------------------------- Lock gained
ddlpa: opened /dev/hdc
slept 1 seconds of 1
Ted Ts'o provided program open-cd-excl which allows to explore open(2) on
device files with combinations of read-write, O_EXCL, and fcntl().
(This does not mean that Ted endorsed our project yet. He helps exploring.)
Friendly in the sense of DDLP-A would be any run which uses at least one of
the options -e (i.e. O_EXCL) or -f (i.e. F_SETLK, applied to a file
descriptor which was obtained from a standard device file path).
The code is available under GPL at
To be compiled by
cc -g -Wall -o open-cd-excl open-cd-excl.c
-e : open O_EXCL
-f : aquire lock by fcntl(F_SETLK) after sucessful open
-i : do not wait in case of success but exit 0 immediately
-r : open O_RDONLY , with -f use F_RDLCK
-w : open O_RDWR , with -f use F_WRLCK
plus the path of the devce file to open.
Friendly Programs would use gestures like:
./open-cd-excl -e -r /dev/sr0
./open-cd-excl -e -w /dev/sg1
./open-cd-excl -e -w /dev/black-drive
./open-cd-excl -f -r /dev/sg1
./open-cd-excl -e -f -w /dev/sr0
Ignorant programs would use and cause potential trouble by:
./open-cd-excl -r /dev/sr0
./open-cd-excl -w /dev/sg1
./open-cd-excl -f -w /dev/black-drive
where "/dev/black-drive" is _not_ a symbolic link to
any of /dev/sr* /dev/scd* /dev/sg* /dev/hd*, but has an own inode.
Prone to failure without further reason is:
./open-cd-excl -e -r /dev/sg1
This protocol relies on proxy lock files in some filesystem directory. It can
be embedded into DDLP-A or it ican be used be used standalone, outside DDLP-A.
DDLP-A shall be kept by DDLP-B from trying to access any device file which
might already be in use. There is a problematic gesture in DDLP-A when SCSI
address parameters are to be retrieved. For now this gesture seems to be
harmless. But one never knows.
There is a proxy file locking protocol described in FHS:
But it has shortcommings:
- Stale locks are possible.
- Much info is missing about the occupying process: host id, program, purpose
- It is necessary to create a file (using the _old_ meaning of O_EXCL flag ?).
- No way to indicate difference between exclusive and shared locks.
- Relies entirely on basename of device file path.
- /var/lock/ is not available early during system start and often has
restrictive permission settings.
The stale locks and the clear prescriptions in FHS make /var/lock/ entirely
unsuitable for our purpose.
DDLP-B rather defines a "path prefix" which is advised to be
This prefix will get appended "device specific suffixes" and then form the path
of a "lockfile".
Not the existence of a lockfile but its occupation by an fcntl(F_SETLK) will
constitute a lock. Lockfiles may get prepared by the sysadmin in directories
where normal users are not allowed to create new files. Their rw-permissions
then act as additional access restriction to the device files.
The use of fcntl(F_SETLK) will prevent any stale locks after the process ended.
It will also allow to obtain shared locks as well as exclusive locks.
There are several classes of device specific suffixes:
- Device file path suffix. "/" gets replaced by "_-". Eventual "_-" in path
gets replaced by "_-_-".
E.g.: "_-dev_-sr0" , "_-mydevs_-burners_-nec"
- st_rdev suffix. A hex representation of struct stat.st_rdev. Capital letters.
The number of characters is pare with at most one leading 0. I.e. bytewise
printf("%2.2X") beginning with the highest order byte that is not zero.
E.g. : "0B01", "2200", "01000000000004001"
- SCSI parameter suffix. A tuple of decimal numbers representing the SCSI
address if applicable for the device at all. On Linux this are the four
numbers Host,Channel,Id,Lun obtained by ioctl(SCSI_IOCTL_GET_IDLUN).
The separator is the minor letter "s".
E.g. "1s0s0s0", "0s0s3s0"
If a lockfile does not exist and cannot be created then this shall not keep
a program from working on a device. But if a lockfile exists and if permissions
or locking state do not allow to obtain a lock of the appropirate type, then
this shall prevent any opening of device file in question resp. shall cause
immediate close(2) of an already opened device file.
The vulnerable programs shall not start their operation before they locked a
wide collection of drive representations.
Non-vulnerable programs shall take care to lock at least the suffix resulting
from the path they will be using and the suffix of the st_rdev from that path.
The latter is to be obtained by call stat(2).
>>> Vulnerable program shall use SCSI parameter suffixes to ensure that the search
>>> for further paths and st_rdev representations of the same device does not
>>> disturb
If it is sure that the device has valid SCSI address parameters then these
should be obtained first and the SCSI parameter suffix should be locked before
any further activity is started. If done so, then the open(2) flags shall
include O_NDELAY to avoid side effect. O_NDELAY may be revoked later by
fcntl(2) F_GETFL,F_SETFL.
This gesture is mandatory only for vulnerable
programs in order to obtain more path and st_rdev suffixes.
Example: Device file path "/dev/sr1"