Hey,
In stdio, which lock are you supposed to take first? The global
sfp_mutex or the per-FILE lock?
In __sfp() we hold sfp_mutex while iterating through the pool (unsure
what else to call it) of FILEs. No two threads can modify the pool at
the same time:
111 _MUTEX_LOCK(&__sfp_mutex);
112 for (g = &__sglue; g != NULL; g = g->next) {
113 for (fp = g->iobs, n = g->niobs; --n >= 0; fp++)
114 if (fp->_flags == 0)
115 goto found;
116 }
117
118 /* release lock while mallocing */
119 _MUTEX_UNLOCK(&__sfp_mutex);
120 if ((g = moreglue(NDYNAMIC)) == NULL)
121 return (NULL);
122 _MUTEX_LOCK(&__sfp_mutex);
123 lastglue->next = g;
124 lastglue = g;
125 fp = g->iobs;
126 found:
127 fp->_flags = 1; /* reserve this slot; caller sets real
flags */
128 _MUTEX_UNLOCK(&__sfp_mutex);
Note that we set _flags to 1 to reserve it for the current thread
before leaving sfp_mutex. Note also that we don't take the per-FILE
lock before reading each FILE's _flags.
Then look at fclose(3):
39 int
40 fclose(FILE *fp)
41 {
42 int r;
43
44 if (fp->_flags == 0) { /* not open! */
45 errno = EBADF;
46 return (EOF);
47 }
48 FLOCKFILE(fp);
49 WCIO_FREE(fp);
50 r = fp->_flags & __SWR ? __sflush(fp) : 0;
51 if (fp->_close != NULL && (*fp->_close)(fp->_cookie) < 0)
52 r = EOF;
53 if (fp->_flags & __SMBF)
54 free((char *)fp->_bf._base);
55 if (HASUB(fp))
56 FREEUB(fp);
57 if (HASLB(fp))
58 FREELB(fp);
59 fp->_r = fp->_w = 0; /* Mess up if reaccessed. */
61 fp->_flags = 0; /* Release this FILE for reuse. */
63 FUNLOCKFILE(fp);
64 return (r);
65 }
66 DEF_STRONG(fclose);
We check if _flags is zero without any lock. I'm unsure if this is
safe.
However, we then clean up under the FILE's lock and set _flags to zero
without sfp_mutex.
... that can't be right.
So, what to do? My immediate thought was to export sfp_mutex and
enter it before writing _flags (diff attached). But then the global
sfp_mutex is "higher" in the locking hierarchy than the per-FILE lock.
That doesn't seem quite right to me.
We also modify _flags all over stdio without sfp_mutex, so the rule is
inconsistent.
Another possibility is to take the per-FILE lock when examining each
FILE's _flags during __sfp(). That would be costlier, but then the
hierarchy would be reversed.
Thoughts?
Index: findfp.c
===================================================================
RCS file: /cvs/src/lib/libc/stdio/findfp.c,v
retrieving revision 1.19
diff -u -p -r1.19 findfp.c
--- findfp.c 5 Apr 2016 04:29:21 -0000 1.19
+++ findfp.c 26 Nov 2020 00:17:16 -0000
@@ -42,6 +42,7 @@
#include "thread_private.h"
int __sdidinit;
+void *__sfp_mutex;
#define NDYNAMIC 10 /* add ten more whenever necessary */
@@ -56,7 +57,6 @@ static FILE usual[FOPEN_MAX - 3];
static struct __sfileext usualext[FOPEN_MAX - 3];
static struct glue uglue = { 0, FOPEN_MAX - 3, usual };
static struct glue *lastglue = &uglue;
-static void *sfp_mutex;
static struct __sfileext __sFext[3];
FILE __sF[3] = {
@@ -108,7 +108,7 @@ __sfp(void)
if (!__sdidinit)
__sinit();
- _MUTEX_LOCK(&sfp_mutex);
+ _MUTEX_LOCK(&__sfp_mutex);
for (g = &__sglue; g != NULL; g = g->next) {
for (fp = g->iobs, n = g->niobs; --n >= 0; fp++)
if (fp->_flags == 0)
@@ -116,16 +116,16 @@ __sfp(void)
}
/* release lock while mallocing */
- _MUTEX_UNLOCK(&sfp_mutex);
+ _MUTEX_UNLOCK(&__sfp_mutex);
if ((g = moreglue(NDYNAMIC)) == NULL)
return (NULL);
- _MUTEX_LOCK(&sfp_mutex);
+ _MUTEX_LOCK(&__sfp_mutex);
lastglue->next = g;
lastglue = g;
fp = g->iobs;
found:
fp->_flags = 1; /* reserve this slot; caller sets real flags */
- _MUTEX_UNLOCK(&sfp_mutex);
+ _MUTEX_UNLOCK(&__sfp_mutex);
fp->_p = NULL; /* no current pointer */
fp->_w = 0; /* nothing to read or write */
fp->_r = 0;
Index: fclose.c
===================================================================
RCS file: /cvs/src/lib/libc/stdio/fclose.c,v
retrieving revision 1.10
diff -u -p -r1.10 fclose.c
--- fclose.c 31 Aug 2015 02:53:57 -0000 1.10
+++ fclose.c 26 Nov 2020 00:17:16 -0000
@@ -57,7 +57,9 @@ fclose(FILE *fp)
if (HASLB(fp))
FREELB(fp);
fp->_r = fp->_w = 0; /* Mess up if reaccessed. */
+ _MUTEX_LOCK(&__sfp_mutex);
fp->_flags = 0; /* Release this FILE for reuse. */
+ _MUTEX_UNLOCK(&__sfp_mutex);
FUNLOCKFILE(fp);
return (r);
}
Index: local.h
===================================================================
RCS file: /cvs/src/lib/libc/stdio/local.h,v
retrieving revision 1.25
diff -u -p -r1.25 local.h
--- local.h 23 May 2016 00:21:48 -0000 1.25
+++ local.h 26 Nov 2020 00:17:16 -0000
@@ -66,6 +66,7 @@ int __vfwprintf(FILE * __restrict, const
int __vfwscanf(FILE * __restrict, const wchar_t * __restrict, __va_list);
extern int __sdidinit;
+extern void *__sfp_mutex;
__END_HIDDEN_DECLS
/*