sed: correctly handle 'w FILE' commands writing to the same file

function                                             old     new   delta
sed_xfopen_w                                           -      84     +84

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/editors/sed.c b/editors/sed.c
index e8c82ac..48b0dbf 100644
--- a/editors/sed.c
+++ b/editors/sed.c
@@ -97,6 +97,12 @@
 	OPT_in_place = 1 << 0,
 };
 
+struct sed_FILE {
+	struct sed_FILE *next; /* Next (linked list, NULL terminated) */
+	const char *fname;
+	FILE *fp;
+};
+
 /* Each sed command turns into one of these structures. */
 typedef struct sed_cmd_s {
 	/* Ordered by alignment requirements: currently 36 bytes on x86 */
@@ -151,6 +157,11 @@
 	/* linked list of append lines */
 	llist_t *append_head;
 
+	/* linked list of FILEs opened for 'w' and s///w'.
+	 * Needed to handle duplicate fnames: sed '/a/w F;/b/w F'
+	 */
+	struct sed_FILE *FILE_head;
+
 	char *add_cmd_line;
 
 	struct pipeline {
@@ -211,6 +222,22 @@
 void sed_free_and_close_stuff(void);
 #endif
 
+static FILE *sed_xfopen_w(const char *fname)
+{
+	struct sed_FILE **pp = &G.FILE_head;
+	struct sed_FILE *cur;
+	while ((cur = *pp) != NULL) {
+		if (strcmp(cur->fname, fname) == 0)
+			return cur->fp;
+		pp = &cur->next;
+	}
+	*pp = cur = xzalloc(sizeof(*cur));
+	/*cur->next = NULL; - already is */
+	cur->fname = xstrdup(fname);
+	cur->fp = xfopen_for_write(fname);
+	return cur->fp;
+}
+
 /* If something bad happens during -i operation, delete temp file */
 
 static void cleanup_outname(void)
@@ -446,7 +473,7 @@
 		{
 			char *fname;
 			idx += parse_file_cmd(/*sed_cmd,*/ substr+idx+1, &fname);
-			sed_cmd->sw_file = xfopen_for_write(fname);
+			sed_cmd->sw_file = sed_xfopen_w(fname);
 			sed_cmd->sw_last_char = '\n';
 			free(fname);
 			break;
@@ -561,7 +588,7 @@
 		}
 		cmdstr += parse_file_cmd(/*sed_cmd,*/ cmdstr, &sed_cmd->string);
 		if (sed_cmd->cmd == 'w') {
-			sed_cmd->sw_file = xfopen_for_write(sed_cmd->string);
+			sed_cmd->sw_file = sed_xfopen_w(sed_cmd->string);
 			sed_cmd->sw_last_char = '\n';
 		}
 	}
diff --git a/testsuite/sed.tests b/testsuite/sed.tests
index 2b78c9b..e62b839 100755
--- a/testsuite/sed.tests
+++ b/testsuite/sed.tests
@@ -405,6 +405,15 @@
 	"" \
 	"abca\n"
 
+# This only works if file name is exactly the same.
+# For example, w FILE; w ./FILE won't work.
+testing "sed understands duplicate file name" \
+	"sed -n -e '/a/w sed.output' -e '/c/w sed.output' 2>&1 && cat sed.output && rm sed.output" \
+	"a\nc\n" \
+	"" \
+	"a\nb\nc\n"
+
+
 # testing "description" "commands" "result" "infile" "stdin"
 
 exit $FAILCOUNT