Browse code

pkg/jsonmessage: Avoid undefined ANSI escape codes.

The ANSI escape codes \e[0A (cursor up 0 lines) and \e[0B (cursor down 0 lines)
are not well defined and are treated differently by different terminals. In
particular xterm treats 0 as a missing parameter and therefore defaults to 1,
whereas rxvt-unicode treats these escapes as a request to move 0 lines.

However the use of these codes is unnecessary and were really just hiding the
fact that we were not correctly computing diff when adding a new line. Having
added the new line to the ids map and output the corresponding \n we need to
then calculate a correct diff of 1 rather than leaving it as the default 0
(which xterm then interprets as 1). The fix is to pull the diff calculation out
of the else case and to always do it.

With this in place we can then avoid outputting escapes for moving 0 lines.
Actually diff should never be 0 to start with any more, but check to be safe.

This fixes corruption of `docker pull` seen with rxvt-unicode (and likely other
terminals in that family) seen in #28111. Tested with rxvt-unicode
($TERM=rxvt-unicode), xterm ($TERM=xterm), mlterm ($TERM=mlterm) and aterm
($TERM=kterm).

The test cases have been updated to match the new behaviour.

Signed-off-by: Ian Campbell <ian.campbell@docker.com>

Ian Campbell authored on 2016/11/10 01:51:18
Showing 2 changed files
... ...
@@ -189,13 +189,9 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
189 189
 				if isTerminal {
190 190
 					fmt.Fprintf(out, "\n")
191 191
 				}
192
-			} else {
193
-				diff = len(ids) - line
194 192
 			}
195
-			if isTerminal {
196
-				// NOTE: this appears to be necessary even if
197
-				// diff == 0.
198
-				// <ESC>[{diff}A = move cursor up diff rows
193
+			diff = len(ids) - line
194
+			if isTerminal && diff > 0 {
199 195
 				fmt.Fprintf(out, "%c[%dA", 27, diff)
200 196
 			}
201 197
 		} else {
... ...
@@ -207,10 +203,7 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
207 207
 			ids = make(map[string]int)
208 208
 		}
209 209
 		err := jm.Display(out, isTerminal)
210
-		if jm.ID != "" && isTerminal {
211
-			// NOTE: this appears to be necessary even if
212
-			// diff == 0.
213
-			// <ESC>[{diff}B = move cursor down diff rows
210
+		if jm.ID != "" && isTerminal && diff > 0 {
214 211
 			fmt.Fprintf(out, "%c[%dB", 27, diff)
215 212
 		}
216 213
 		if err != nil {
... ...
@@ -205,17 +205,17 @@ func TestDisplayJSONMessagesStream(t *testing.T) {
205 205
 		// Without progress, with ID
206 206
 		"{ \"id\": \"ID\",\"status\": \"status\" }": {
207 207
 			"ID: status\n",
208
-			fmt.Sprintf("ID: status\n%c[%dB", 27, 0),
208
+			fmt.Sprintf("ID: status\n"),
209 209
 		},
210 210
 		// With progress
211 211
 		"{ \"id\": \"ID\", \"status\": \"status\", \"progress\": \"ProgressMessage\" }": {
212 212
 			"ID: status ProgressMessage",
213
-			fmt.Sprintf("\n%c[%dAID: status ProgressMessage%c[%dB", 27, 0, 27, 0),
213
+			fmt.Sprintf("\n%c[%dAID: status ProgressMessage%c[%dB", 27, 1, 27, 1),
214 214
 		},
215 215
 		// With progressDetail
216 216
 		"{ \"id\": \"ID\", \"status\": \"status\", \"progressDetail\": { \"Current\": 1} }": {
217 217
 			"", // progressbar is disabled in non-terminal
218
-			fmt.Sprintf("\n%c[%dA%c[2K\rID: status      1 B\r%c[%dB", 27, 0, 27, 27, 0),
218
+			fmt.Sprintf("\n%c[%dA%c[2K\rID: status      1 B\r%c[%dB", 27, 1, 27, 27, 1),
219 219
 		},
220 220
 	}
221 221
 	for jsonMessage, expectedMessages := range messages {