summaryrefslogtreecommitdiff
blob: 3b53c73c645c9837d8041cce36684e56d0f8d2a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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