From 6568d364f4d21df69027319f55bcc903bfb32e0a Mon Sep 17 00:00:00 2001
From: Jim Meyering <meyering@fb.com>
Date: Sat, 10 Jul 2021 17:34:39 -0700
Subject: [PATCH 10/44] sed: avoid potential double-fclose

Upon a failed temp file fclose, do_ck_fclose would call panic,
which would then attempt to fclose and unlink that same pointer.
Caught by gcc's new -Wanalyzer-double-fclose.
* sed/utils.c (struct open_file) [fclose_failed]: New member.
(panic): Don't double-close.
(register_open_file): Clear new member.
(mark_as_fclose_failed): Use new member to avoid double fclose.
(do_ck_fclose): Call mark_as_fclose_failed upon fclose failure.
---
 sed/utils.c | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/sed/utils.c b/sed/utils.c
index 699fd85..98b6333 100644
--- a/sed/utils.c
+++ b/sed/utils.c
@@ -47,6 +47,7 @@ struct open_file
     char *name;
     struct open_file *link;
     unsigned temp : 1;
+    unsigned fclose_failed : 1;
   };
 
 static struct open_file *open_files = NULL;
@@ -70,7 +71,8 @@ panic (const char *str, ...)
     {
       if (open_files->temp)
         {
-          fclose (open_files->fp);
+          if (!open_files->fclose_failed)
+            fclose (open_files->fp);
           errno = 0;
           unlink (open_files->name);
           if (errno != 0)
@@ -131,6 +133,7 @@ register_open_file (FILE *fp, const char *name)
   p->name = xstrdup (name);
   p->fp = fp;
   p->temp = false;
+  p->fclose_failed = false;
 }
 
 /* Panic on failing fopen */
@@ -252,6 +255,21 @@ ck_fflush (FILE *stream)
     panic ("couldn't flush %s: %s", utils_fp_name (stream), strerror (errno));
 }
 
+/* If we've failed to close a file in open_files whose "fp" member
+   is the same as FP, mark its entry as fclose_failed.  */
+static void
+mark_as_fclose_failed (FILE *fp)
+{
+  for (struct open_file *p = open_files; p; p = p->link)
+    {
+      if (p->fp == fp)
+        {
+          p->fclose_failed = true;
+          break;
+        }
+    }
+}
+
 /* Panic on failing fclose */
 void
 ck_fclose (FILE *stream)
@@ -293,7 +311,13 @@ do_ck_fclose (FILE *fp)
   clearerr (fp);
 
   if (fclose (fp) == EOF)
-    panic ("couldn't close %s: %s", utils_fp_name (fp), strerror (errno));
+    {
+      /* Mark as already fclose-failed, so we don't attempt to fclose it
+         a second time via panic.  */
+      mark_as_fclose_failed (fp);
+
+      panic ("couldn't close %s: %s", utils_fp_name (fp), strerror (errno));
+    }
 }
 
 /* Follow symlink and panic if something fails.  Return the ultimate
-- 
2.27.0

