blob: 3b53c73c645c9837d8041cce36684e56d0f8d2a9 [file] [log] [blame]
https://crbug.com/508713
https://lists.gnu.org/archive/html/info-mtools/2016-11/msg00000.html
From 04df65ed797e47da5b423c7f9aec99d82dfde400 Mon Sep 17 00:00:00 2001
From: Mike Frysinger <vapier@chromium.org>
Date: Wed, 7 Sep 2016 12:33:42 -0400
Subject: [PATCH] add support for retrying device locking
When running syslinux's install phase, it will run a bunch of mtools
commands in quick succession. If you're on a fast enough machine, it
can often fail with errors like:
plain floppy: device "/proc/2908/fd/3" busy (Resource temporarily unavailable):
Cannot initialize 'S:'
Bad target s:/ldlinux.sys
syslinux: failed to create ldlinux.sys
The issue is that after some of the mtools calls, the kernel notices
that the fs image has changed, so it notifies userspace. This wakes
up udev which grabs a lock on the device to rescan it for changes
(e.g. updated fs metadata like UUID). The udev phase does not finish
before syslinux fires off another mtools call which means mtools now
fails with a locking error.
You can recreate this with a simple test:
- loop mount a fat fs image
- open the loop device for writing
- generate a mtools.conf pointing the file to /proc/$pid/fd/$fd
- run mattrib && mcopy
- see udev open/lock the loop device after mattrib runs to probe it
- see mcopy fail because udev is still holding the lock
To fix things, we teach mtools to retry its locking calls temporarily.
If it still fails after a timeout, we abort like normal. We also make
this behavior configurable by adding a new global timeout option.
---
config.c | 2 ++
mtools.h | 1 +
mtools.texi | 7 +++++++
mtools.tmpl.5 | 4 ++++
plain_io.c | 10 ++++++++++
xdf_io.c | 11 +++++++++++
6 files changed, 35 insertions(+)
diff --git a/config.c b/config.c
index f08688399d1d..ea4178452f6a 100644
--- a/config.c
+++ b/config.c
@@ -63,6 +63,7 @@ unsigned int mtools_no_vfat=0;
unsigned int mtools_numeric_tail=1;
unsigned int mtools_dotted_dir=0;
unsigned int mtools_twenty_four_hour_clock=1;
+unsigned int mtools_lock_timeout=30;
unsigned int mtools_default_codepage=850;
const char *mtools_date_string="yyyy-mm-dd";
char *country_string=0;
@@ -90,6 +91,7 @@ static switches_t global_switches[] = {
(caddr_t) &mtools_twenty_four_hour_clock, T_UINT },
{ "MTOOLS_DATE_STRING",
(caddr_t) &mtools_date_string, T_STRING },
+ { "MTOOLS_LOCK_TIMEOUT", (caddr_t) &mtools_lock_timeout, T_UINT },
{ "DEFAULT_CODEPAGE", (caddr_t) &mtools_default_codepage, T_UINT }
};
diff --git a/mtools.h b/mtools.h
index ef98e942ee2c..fa8c1bdc8a1b 100644
--- a/mtools.h
+++ b/mtools.h
@@ -188,6 +188,7 @@ extern unsigned int mtools_ignore_short_case;
extern unsigned int mtools_no_vfat;
extern unsigned int mtools_numeric_tail;
extern unsigned int mtools_dotted_dir;
+extern unsigned int mtools_lock_timeout;
extern unsigned int mtools_twenty_four_hour_clock;
extern const char *mtools_date_string;
extern unsigned int mtools_rate_0, mtools_rate_any;
diff --git a/mtools.texi b/mtools.texi
index 1085789c1cb6..1c7ad94d40f9 100644
--- a/mtools.texi
+++ b/mtools.texi
@@ -658,6 +658,10 @@ DOSEMU image files.
@vindex MTOOLS_FAT_COMPATIBILITY
@vindex MTOOLS_LOWER_CASE
@vindex MTOOLS_NO_VFAT
+@vindex MTOOLS_DOTTED_DIR
+@vindex MTOOLS_NAME_NUMERIC_TAIL
+@vindex MTOOLS_TWENTY_FOUR_HOUR_CLOCK
+@vindex MTOOLS_LOCK_TIMEOUT
@cindex FreeDOS
Global flags may be set to 1 or to 0.
@@ -692,6 +696,9 @@ clash would have happened.
@item MTOOLS_TWENTY_FOUR_HOUR_CLOCK
If 1, uses the European notation for times (twenty four hour clock),
else uses the UK/US notation (am/pm)
+@item MTOOLS_LOCK_TIMEOUT
+How long, in seconds, to wait for a locked device to become free.
+Defaults to 30.
@end table
Example:
diff --git a/mtools.tmpl.5 b/mtools.tmpl.5
index 565fdd7513aa..8cdaaf2ba929 100644
--- a/mtools.tmpl.5
+++ b/mtools.tmpl.5
@@ -106,6 +106,10 @@ clash would have happened.
\&\fR\&\f(CWMTOOLS_TWENTY_FOUR_HOUR_CLOCK\fR\
If 1, uses the European notation for times (twenty four hour clock),
else uses the UK/US notation (am/pm)
+.TP
+\&\fR\&\f(CWMTOOLS_LOCK_TIMEOUT\fR\
+How long, in seconds, to wait for a locked device to become free.
+Defaults to 30.
.PP
Example:
Inserting the following line into your configuration file instructs
diff --git a/plain_io.c b/plain_io.c
index c9d8418b8b4d..3dc035c9ce92 100644
--- a/plain_io.c
+++ b/plain_io.c
@@ -632,7 +632,17 @@ APIRET rc;
#ifndef __CYGWIN__
#ifndef OS_mingw32msvc
/* lock the device on writes */
+ retry:
if (locked && lock_dev(This->fd, mode == O_RDWR, dev)) {
+ /* retry the lock in case another system process (e.g. udev)
+ * has temporarily locked the device. this happens when you
+ * run multiple mtools commands at once which triggers the
+ * system to lock/rescan/unlock. */
+ static int retries = 0;
+ if (errno == EAGAIN && retries++ < mtools_lock_timeout * 10) {
+ usleep(100);
+ goto retry;
+ }
if(errmsg)
#ifdef HAVE_SNPRINTF
snprintf(errmsg,199,
diff --git a/xdf_io.c b/xdf_io.c
index f0db3b3d9f38..8f64f6348f0c 100644
--- a/xdf_io.c
+++ b/xdf_io.c
@@ -638,7 +638,18 @@ Stream_t *XdfOpen(struct device *dev, char *name,
goto exit_2;
/* lock the device on writes */
+ retry:
if (lock_dev(This->fd, mode == O_RDWR, dev)) {
+ /* retry the lock in case another system process (e.g. udev)
+ * has temporarily locked the device. this happens when you
+ * run multiple mtools commands at once which triggers the
+ * system to lock/rescan/unlock. */
+ static int retries = 0;
+ if (errno == EAGAIN && retries++ < mtools_lock_timeout * 10) {
+ usleep(100);
+ goto retry;
+ }
+
#ifdef HAVE_SNPRINTF
snprintf(errmsg,199,"xdf floppy: device \"%s\" busy:",
dev->name);
--
2.9.0