subshell output swallowed (patch)

Egmont Koblinger egmont at uhulinux.hu
Mon Jan 2 18:20:34 UTC 2006


Hi,

When an external command is executed in mc with subshell support, quite
often some characters of its output are swallowed. Maybe it's most noticable
with the "ls" command in a directory where there are a lot of files and "ls"
outputs them in more columns. Just launch a terminal emulator, start mc,
press ^O so that the panels disappear, and run "ls" a several times. Sure
you'll see some occasions when the columns are not lined up correctly.

For example, in a 70-column wide terminal, standing in the source tree of
Python-2.4.2 in the Lib directory, an ls now produced this output to me:

[...]
fileinput.py        plat-irix6            toaiff.py
fnmatch.py          plat-linux2           tokenize.py
formatter.py        plat-mac              token.py
fpformat.py    plat-netbsd1          traceback.py
ftplib.py           plat-next3            trace.py
__future__.py       plat-os2emx           tzparse.py
getopt.py           plat-riscos           types.py
[...]

The bug is reproducible in different terminal emulators, and does not occur
if "ls" is executed outside mc, or inside "mc -u".

An "strace -e trace=read,write" of a similar case showed this:

[...]
read(4, "\33[0mcopy.py\33[0m            \33[0mi"..., 100) = 100 
write(1, "\33[0mcopy.py\33[0m            \33[0mi"..., 100) = 100
read(4, "m         \33[0m_strptime.py\33[0m  "..., 100) = 62
write(1, "m         \33[0m_strptime.py\33[0m  "..., 62) = 62
read(4, "\33[0mcopy_reg.py\33[0m        \33[0mi"..., 100) = 100
write(1, "\33[0mcopy_reg.py\33[0m        \33[0mi"..., 100) = 53
--- SIGCHLD (Child exited) @ 0 (0) ---
read(4, "py\33[0m        \33[0msubprocess.py\33"..., 100) = 100
write(1, "py\33[0m        \33[0msubprocess.py\33"..., 100) = 100
read(4, "mghdr.py\33[0m           \33[0mpipes"..., 100) = 100   
write(1, "mghdr.py\33[0m           \33[0mpipes"..., 100) = 100  
[...]

So, when "ls" exits, mc receives a sigchild, which interrupts the write call
that transfers the output of ls from the subshell to its real output, but
the interrupted call is not finished, in this particular case 47 bytes are
skipped.

See the attached patch which fixes this problem.



bye,

Egmont
-------------- next part --------------
diff -Naur mc-4.6.1.orig/ChangeLog mc-4.6.1/ChangeLog
--- mc-4.6.1.orig/ChangeLog	2005-07-23 18:51:11.000000000 +0200
+++ mc-4.6.1/ChangeLog	2006-01-02 19:00:41.000000000 +0100
@@ -1,3 +1,8 @@
+2006-01-02  Egmont Koblinger  <egmont at uhulinux.hu>
+
+	* src/subshell.c: restart write() calls interrupted by sigchld
+	which lead to some bytes being swallowed from the subshell's output.
+
 2005-07-23  Roland Illig  <roland.illig at gmx.de>
 
 	* configure.ac: getgrouplist() is not used anymore, so there' no
diff -Naur mc-4.6.1.orig/src/subshell.c mc-4.6.1/src/subshell.c
--- mc-4.6.1.orig/src/subshell.c	2005-06-07 11:19:19.000000000 +0200
+++ mc-4.6.1/src/subshell.c	2006-01-02 18:58:01.000000000 +0100
@@ -150,6 +150,30 @@
 
 
 /*
+ *  Write all data, even if the write() call is interrupted.
+ */
+static ssize_t
+write_all (int fd, const void *buf, size_t count)
+{
+    ssize_t ret;
+    ssize_t written = 0;
+    while (count > 0) {
+	ret = write (fd, buf, count);
+	if (ret < 0) {
+	    if (errno == EINTR) {
+		continue;
+	    } else {
+		return written > 0 ? written : ret;
+	    }
+	}
+	buf += ret;
+	count -= ret;
+	written += ret;
+    }
+    return written;
+}
+
+/*
  *  Prepare child process to running the shell and run it.
  *
  *  Modifies the global variables (in the child process only):
@@ -476,7 +500,7 @@
 		    tcsh_fifo);
 	break;
     }
-    write (subshell_pty, precmd, strlen (precmd));
+    write_all (subshell_pty, precmd, strlen (precmd));
 
     /* Wait until the subshell has started up and processed the command */
 
@@ -533,16 +557,16 @@
 	    subshell_state = ACTIVE;
 	    /* FIXME: possibly take out this hack; the user can
 	       re-play it by hitting C-hyphen a few times! */
-	    write (subshell_pty, " \b", 2);  /* Hack to make prompt reappear */
+	    write_all (subshell_pty, " \b", 2);  /* Hack to make prompt reappear */
 	}
     }
     else  /* MC has passed us a user command */
     {
 	if (how == QUIETLY)
-	    write (subshell_pty, " ", 1);
+	    write_all (subshell_pty, " ", 1);
 	/* FIXME: if command is long (>8KB ?) we go comma */
-	write (subshell_pty, command, strlen (command));
-	write (subshell_pty, "\n", 1);
+	write_all (subshell_pty, command, strlen (command));
+	write_all (subshell_pty, "\n", 1);
 	subshell_state = RUNNING_COMMAND;
 	subshell_ready = FALSE;
     }
@@ -767,21 +791,21 @@
 
     /* The initial space keeps this out of the command history (in bash
        because we set "HISTCONTROL=ignorespace") */
-    write (subshell_pty, " cd ", 4);
+    write_all (subshell_pty, " cd ", 4);
     if (*directory) {
 	char *temp = subshell_name_quote (directory);
 	if (temp) {
-	    write (subshell_pty, temp, strlen (temp));
+	    write_all (subshell_pty, temp, strlen (temp));
 	    g_free (temp);
 	} else {
 	    /* Should not happen unless the directory name is so long
 	       that we don't have memory to quote it.  */
-	    write (subshell_pty, ".", 1);
+	    write_all (subshell_pty, ".", 1);
 	}
     } else {
-	write (subshell_pty, "/", 1);
+	write_all (subshell_pty, "/", 1);
     }
-    write (subshell_pty, "\n", 1);
+    write_all (subshell_pty, "\n", 1);
 
     subshell_state = RUNNING_COMMAND;
     feed_subshell (QUIETLY, FALSE);
@@ -952,7 +976,7 @@
 	    }
 
 	    if (how == VISIBLY)
-		write (STDOUT_FILENO, pty_buffer, bytes);
+		write_all (STDOUT_FILENO, pty_buffer, bytes);
 	}
 
 	else if (FD_ISSET (subshell_pipe[READ], &read_set))
@@ -994,13 +1018,13 @@
 
 	    for (i = 0; i < bytes; ++i)
 		if (pty_buffer[i] == subshell_switch_key) {
-		    write (subshell_pty, pty_buffer, i);
+		    write_all (subshell_pty, pty_buffer, i);
 		    if (subshell_ready)
 			subshell_state = INACTIVE;
 		    return TRUE;
 		}
 
-	    write (subshell_pty, pty_buffer, bytes);
+	    write_all (subshell_pty, pty_buffer, bytes);
 	    subshell_ready = FALSE;
 	} else {
 	    return FALSE;


More information about the mc-devel mailing list