Skip to content
30 changes: 24 additions & 6 deletions src/uu/comm/src/comm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use std::cmp::Ordering;
use std::ffi::OsString;
use std::fs::{File, metadata};
use std::io::{self, BufRead, BufReader, Read, StdinLock, stdin};
use std::io::{self, BufRead, BufReader, BufWriter, Read, StdinLock, Write, stdin};
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError};
Expand Down Expand Up @@ -186,6 +186,8 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches)
let delim_col_2 = delim.repeat(width_col_1);
let delim_col_3 = delim.repeat(width_col_1 + width_col_2);

let mut writer = BufWriter::new(io::stdout().lock());

let ra = &mut Vec::new();
let mut na = a.read_line(ra);
let rb = &mut Vec::new();
Expand Down Expand Up @@ -234,7 +236,9 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches)
break;
}
if !opts.get_flag(options::COLUMN_1) {
print!("{}", String::from_utf8_lossy(ra));
writer
.write_all(ra)
.map_err_context(|| "write error".to_string())?;
}
ra.clear();
na = a.read_line(ra);
Expand All @@ -245,7 +249,12 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches)
break;
}
if !opts.get_flag(options::COLUMN_2) {
print!("{delim_col_2}{}", String::from_utf8_lossy(rb));
writer
.write_all(delim_col_2.as_bytes())
.map_err_context(|| "write error".to_string())?;
writer
.write_all(rb)
.map_err_context(|| "write error".to_string())?;
}
rb.clear();
nb = b.read_line(rb);
Expand All @@ -257,7 +266,12 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches)
break;
}
if !opts.get_flag(options::COLUMN_3) {
print!("{delim_col_3}{}", String::from_utf8_lossy(ra));
writer
.write_all(delim_col_3.as_bytes())
.map_err_context(|| "write error".to_string())?;
writer
.write_all(ra)
.map_err_context(|| "write error".to_string())?;
}
ra.clear();
rb.clear();
Expand All @@ -275,12 +289,16 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches)

if opts.get_flag(options::TOTAL) {
let line_ending = LineEnding::from_zero_flag(opts.get_flag(options::ZERO_TERMINATED));
print!(
write!(
writer,
"{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}{}{line_ending}",
translate!("comm-total")
);
)
.map_err_context(|| "write error".to_string())?;
}

writer.flush().ok();

if should_check_order && (checker1.has_error || checker2.has_error) {
// Print the input error message once at the end
if input_error {
Expand Down
24 changes: 24 additions & 0 deletions tests/by-util/test_comm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,3 +648,27 @@ fn test_comm_eintr_handling() {
.stdout_contains("line2")
.stdout_contains("line3");
}

#[test]
fn test_output_lossy_utf8() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

// Create files with invalid UTF-8
// A: \xfe\n\xff\n
// B: \xff\n\xfe\n
at.write_bytes("a", b"\xfe\n\xff\n");
at.write_bytes("b", b"\xff\n\xfe\n");

// GNU comm output (and uutils with fix):
// \xfe\n (col 1)
// \t\t\xff\n (col 3)
// \t\xfe\n (col 2)
// Hex: fe 0a 09 09 ff 0a 09 fe 0a

scene
.ucmd()
.args(&["a", "b"])
.fails() // Fails because of unsorted input
.stdout_is_bytes(b"\xfe\n\t\t\xff\n\t\xfe\n");
}
Loading