mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 11:07:44 +00:00
Merge pull request #1022 from millerjs/cat-domain-sockets
add cat support for unix domain sockets
This commit is contained in:
commit
a03b5279f3
5 changed files with 374 additions and 109 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -93,6 +93,7 @@ dependencies = [
|
||||||
"unexpand 0.0.1",
|
"unexpand 0.0.1",
|
||||||
"unindent 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unindent 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uniq 0.0.1",
|
"uniq 0.0.1",
|
||||||
|
"unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unlink 0.0.1",
|
"unlink 0.0.1",
|
||||||
"uptime 0.0.1",
|
"uptime 0.0.1",
|
||||||
"users 0.0.1",
|
"users 0.0.1",
|
||||||
|
@ -185,6 +186,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
name = "cat"
|
name = "cat"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uucore 0.0.1",
|
"uucore 0.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -725,6 +728,11 @@ dependencies = [
|
||||||
"uucore 0.0.1",
|
"uucore 0.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
|
@ -1123,6 +1131,15 @@ dependencies = [
|
||||||
"uucore 0.0.1",
|
"uucore 0.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unix_socket"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unlink"
|
name = "unlink"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -1259,6 +1276,7 @@ dependencies = [
|
||||||
"checksum nix 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0d95c5fa8b641c10ad0b8887454ebaafa3c92b5cd5350f8fc693adafd178e7b"
|
"checksum nix 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0d95c5fa8b641c10ad0b8887454ebaafa3c92b5cd5350f8fc693adafd178e7b"
|
||||||
"checksum num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "55aabf4e2d6271a2e4e4c0f2ea1f5b07cc589cc1a9e9213013b54a76678ca4f3"
|
"checksum num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "55aabf4e2d6271a2e4e4c0f2ea1f5b07cc589cc1a9e9213013b54a76678ca4f3"
|
||||||
"checksum pretty-bytes 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3095b93999fae14b4e0bb661c53875a441d9058b7b1a7ba2dfebc104d3776349"
|
"checksum pretty-bytes 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3095b93999fae14b4e0bb661c53875a441d9058b7b1a7ba2dfebc104d3776349"
|
||||||
|
"checksum quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0aad603e8d7fb67da22dbdf1f4b826ce8829e406124109e73cf1b2454b93a71c"
|
||||||
"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
|
"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
|
||||||
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
|
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
|
||||||
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
|
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
|
||||||
|
@ -1277,6 +1295,7 @@ dependencies = [
|
||||||
"checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af"
|
"checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af"
|
||||||
"checksum unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6722facc10989f63ee0e20a83cd4e1714a9ae11529403ac7e0afd069abc39e"
|
"checksum unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6722facc10989f63ee0e20a83cd4e1714a9ae11529403ac7e0afd069abc39e"
|
||||||
"checksum unindent 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3508be0ce1bacc38d579b69bffb4b8d469f5af0c388ff4890b2b294e61671ffe"
|
"checksum unindent 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3508be0ce1bacc38d579b69bffb4b8d469f5af0c388ff4890b2b294e61671ffe"
|
||||||
|
"checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564"
|
||||||
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
|
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
|
||||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
"checksum walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780"
|
"checksum walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780"
|
||||||
|
|
|
@ -235,6 +235,9 @@ tempdir="*"
|
||||||
unindent="*"
|
unindent="*"
|
||||||
lazy_static = "*"
|
lazy_static = "*"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
|
unix_socket = "*"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "uutils"
|
name = "uutils"
|
||||||
path = "src/uutils/uutils.rs"
|
path = "src/uutils/uutils.rs"
|
||||||
|
|
|
@ -8,8 +8,12 @@ name = "uu_cat"
|
||||||
path = "cat.rs"
|
path = "cat.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
quick-error = "1.1.0"
|
||||||
uucore = { path="../uucore" }
|
uucore = { path="../uucore" }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
unix_socket = "0.5.0"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cat"
|
name = "cat"
|
||||||
path = "main.rs"
|
path = "main.rs"
|
||||||
|
|
349
src/cat/cat.rs
349
src/cat/cat.rs
|
@ -4,23 +4,127 @@
|
||||||
//
|
//
|
||||||
// (c) Jordi Boggiano <j.boggiano@seld.be>
|
// (c) Jordi Boggiano <j.boggiano@seld.be>
|
||||||
// (c) Evgeniy Klyuchikov <evgeniy.klyuchikov@gmail.com>
|
// (c) Evgeniy Klyuchikov <evgeniy.klyuchikov@gmail.com>
|
||||||
|
// (c) Joshua S. Miller <jsmiller@uchicago.edu>
|
||||||
//
|
//
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate quick_error;
|
||||||
|
#[cfg(unix)]
|
||||||
|
extern crate unix_socket;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
// last synced with: cat (GNU coreutils) 8.13
|
// last synced with: cat (GNU coreutils) 8.13
|
||||||
use std::fs::File;
|
use quick_error::ResultExt;
|
||||||
use std::io::{stdout, stdin, stderr, Write, Read, BufWriter};
|
use std::fs::{metadata, File};
|
||||||
|
use std::io::{self, stdout, stdin, stderr, Write, Read, BufWriter};
|
||||||
use uucore::fs::is_stdin_interactive;
|
use uucore::fs::is_stdin_interactive;
|
||||||
|
|
||||||
|
/// Unix domain socket support
|
||||||
|
#[cfg(unix)] use std::net::Shutdown;
|
||||||
|
#[cfg(unix)] use std::os::unix::fs::FileTypeExt;
|
||||||
|
#[cfg(unix)] use unix_socket::UnixStream;
|
||||||
|
|
||||||
static SYNTAX: &'static str = "[OPTION]... [FILE]...";
|
static SYNTAX: &'static str = "[OPTION]... [FILE]...";
|
||||||
static SUMMARY: &'static str = "Concatenate FILE(s), or standard input, to standard output
|
static SUMMARY: &'static str = "Concatenate FILE(s), or standard input, to standard output
|
||||||
With no FILE, or when FILE is -, read standard input.";
|
With no FILE, or when FILE is -, read standard input.";
|
||||||
static LONG_HELP: &'static str = "";
|
static LONG_HELP: &'static str = "";
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum NumberingMode {
|
||||||
|
NumberNone,
|
||||||
|
NumberNonEmpty,
|
||||||
|
NumberAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
quick_error! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum CatError {
|
||||||
|
/// Wrapper for io::Error with path context
|
||||||
|
Input(err: io::Error, path: String) {
|
||||||
|
display("cat: {0}: {1}", path, err)
|
||||||
|
context(path: &'a str, err: io::Error) -> (err, path.to_owned())
|
||||||
|
cause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for io::Error with no context
|
||||||
|
Output(err: io::Error) {
|
||||||
|
display("cat: {0}", err) from()
|
||||||
|
cause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uknown Filetype classification
|
||||||
|
UnknownFiletype(path: String) {
|
||||||
|
display("cat: {0}: unknown filetype", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// At least one error was encountered in reading or writing
|
||||||
|
EncounteredErrors(count: usize) {
|
||||||
|
display("cat: encountered {0} errors", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Denotes an error caused by trying to `cat` a directory
|
||||||
|
IsDirectory(path: String) {
|
||||||
|
display("cat: {0}: Is a directory", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct OutputOptions {
|
||||||
|
/// Line numbering mode
|
||||||
|
number: NumberingMode,
|
||||||
|
|
||||||
|
/// Suppress repeated empty output lines
|
||||||
|
squeeze_blank: bool,
|
||||||
|
|
||||||
|
/// display TAB characters as `tab`
|
||||||
|
show_tabs: bool,
|
||||||
|
|
||||||
|
/// If `show_tabs == true`, this string will be printed in the
|
||||||
|
/// place of tabs
|
||||||
|
tab: String,
|
||||||
|
|
||||||
|
/// Can be set to show characters other than '\n' a the end of
|
||||||
|
/// each line, e.g. $
|
||||||
|
end_of_line: String,
|
||||||
|
|
||||||
|
/// use ^ and M- notation, except for LF (\\n) and TAB (\\t)
|
||||||
|
show_nonprint: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Represents an open file handle, stream, or other device
|
||||||
|
struct InputHandle {
|
||||||
|
reader: Box<Read>,
|
||||||
|
is_interactive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Concrete enum of recognized file types.
|
||||||
|
///
|
||||||
|
/// *Note*: `cat`-ing a directory should result in an
|
||||||
|
/// CatError::IsDirectory
|
||||||
|
enum InputType {
|
||||||
|
Directory,
|
||||||
|
File,
|
||||||
|
StdIn,
|
||||||
|
SymLink,
|
||||||
|
#[cfg(unix)] BlockDevice,
|
||||||
|
#[cfg(unix)] CharacterDevice,
|
||||||
|
#[cfg(unix)] Fifo,
|
||||||
|
#[cfg(unix)] Socket,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type CatResult<T> = Result<T, CatError>;
|
||||||
|
|
||||||
|
|
||||||
pub fn uumain(args: Vec<String>) -> i32 {
|
pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP)
|
let matches = new_coreopts!(SYNTAX, SUMMARY, LONG_HELP)
|
||||||
.optflag("A", "show-all", "equivalent to -vET")
|
.optflag("A", "show-all", "equivalent to -vET")
|
||||||
|
@ -45,6 +149,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
} else {
|
} else {
|
||||||
NumberingMode::NumberNone
|
NumberingMode::NumberNone
|
||||||
};
|
};
|
||||||
|
|
||||||
let show_nonprint =
|
let show_nonprint =
|
||||||
matches.opts_present(&["A".to_owned(), "e".to_owned(), "t".to_owned(), "v".to_owned()]);
|
matches.opts_present(&["A".to_owned(), "e".to_owned(), "t".to_owned(), "v".to_owned()]);
|
||||||
let show_ends = matches.opts_present(&["E".to_owned(), "A".to_owned(), "e".to_owned()]);
|
let show_ends = matches.opts_present(&["E".to_owned(), "A".to_owned(), "e".to_owned()]);
|
||||||
|
@ -55,101 +160,204 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
files.push("-".to_owned());
|
files.push("-".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
if show_tabs || show_nonprint || show_ends || squeeze_blank ||
|
let can_write_fast = !(show_tabs
|
||||||
number_mode != NumberingMode::NumberNone {
|
|| show_nonprint
|
||||||
write_lines(files,
|
|| show_ends
|
||||||
number_mode,
|
|| squeeze_blank
|
||||||
squeeze_blank,
|
|| number_mode != NumberingMode::NumberNone);
|
||||||
show_ends,
|
|
||||||
show_tabs,
|
let success = if can_write_fast {
|
||||||
show_nonprint);
|
write_fast(files).is_ok()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
write_fast(files);
|
let tab = match show_tabs {
|
||||||
|
true => "^I",
|
||||||
|
false => "\t",
|
||||||
|
}.to_owned();
|
||||||
|
|
||||||
|
let end_of_line = match show_ends {
|
||||||
|
true => "$\n",
|
||||||
|
false => "\n",
|
||||||
|
}.to_owned();
|
||||||
|
|
||||||
|
let options = OutputOptions {
|
||||||
|
end_of_line: end_of_line,
|
||||||
|
number: number_mode,
|
||||||
|
show_nonprint: show_nonprint,
|
||||||
|
show_tabs: show_tabs,
|
||||||
|
squeeze_blank: squeeze_blank,
|
||||||
|
tab: tab,
|
||||||
|
};
|
||||||
|
|
||||||
|
write_lines(files, &options).is_ok()
|
||||||
|
};
|
||||||
|
|
||||||
|
match success {
|
||||||
|
true => 0,
|
||||||
|
false => 1,
|
||||||
}
|
}
|
||||||
pipe_flush!();
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum NumberingMode {
|
/// Classifies the `InputType` of file at `path` if possible
|
||||||
NumberNone,
|
///
|
||||||
NumberNonEmpty,
|
/// # Arguments
|
||||||
NumberAll,
|
///
|
||||||
|
/// * `path` - Path on a file system to classify metadata
|
||||||
|
fn get_input_type(path: &str) -> CatResult<InputType> {
|
||||||
|
if path == "-" {
|
||||||
|
return Ok(InputType::StdIn)
|
||||||
|
}
|
||||||
|
|
||||||
|
match metadata(path).context(path)?.file_type() {
|
||||||
|
#[cfg(unix)] ft if ft.is_block_device() => Ok(InputType::BlockDevice),
|
||||||
|
#[cfg(unix)] ft if ft.is_char_device() => Ok(InputType::CharacterDevice),
|
||||||
|
#[cfg(unix)] ft if ft.is_fifo() => Ok(InputType::Fifo),
|
||||||
|
#[cfg(unix)] ft if ft.is_socket() => Ok(InputType::Socket),
|
||||||
|
ft if ft.is_dir() => Ok(InputType::Directory),
|
||||||
|
ft if ft.is_file() => Ok(InputType::File),
|
||||||
|
ft if ft.is_symlink() => Ok(InputType::SymLink),
|
||||||
|
_ => Err(CatError::UnknownFiletype(path.to_owned()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(path: &str) -> Option<(Box<Read>, bool)> {
|
/// Returns an InputHandle from which a Reader can be accessed or an
|
||||||
|
/// error
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path` - `InputHandler` will wrap a reader from this file path
|
||||||
|
fn open(path: &str) -> CatResult<InputHandle> {
|
||||||
if path == "-" {
|
if path == "-" {
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
return Some((Box::new(stdin) as Box<Read>, is_stdin_interactive()));
|
return Ok(InputHandle {
|
||||||
|
reader: Box::new(stdin) as Box<Read>,
|
||||||
|
is_interactive: is_stdin_interactive(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
match File::open(path) {
|
match get_input_type(path)? {
|
||||||
Ok(f) => Some((Box::new(f) as Box<Read>, false)),
|
InputType::Directory => Err(CatError::IsDirectory(path.to_owned())),
|
||||||
Err(e) => {
|
#[cfg(unix)] InputType::Socket => {
|
||||||
(writeln!(&mut stderr(), "cat: {0}: {1}", path, e.to_string())).unwrap();
|
let socket = UnixStream::connect(path).context(path)?;
|
||||||
None
|
socket.shutdown(Shutdown::Write).context(path)?;
|
||||||
}
|
Ok(InputHandle {
|
||||||
|
reader: Box::new(socket) as Box<Read>,
|
||||||
|
is_interactive: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let file = File::open(path).context(path)?;
|
||||||
|
Ok(InputHandle {
|
||||||
|
reader: Box::new(file) as Box<Read>,
|
||||||
|
is_interactive: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_fast(files: Vec<String>) {
|
/// Writes files to stdout with no configuration. This allows a
|
||||||
|
/// simple memory copy. Returns `Ok(())` if no errors were
|
||||||
|
/// encountered, or an error with the number of errors encountered.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `files` - There is no short circuit when encountiner an error
|
||||||
|
/// reading a file in this vector
|
||||||
|
fn write_fast(files: Vec<String>) -> CatResult<()> {
|
||||||
let mut writer = stdout();
|
let mut writer = stdout();
|
||||||
let mut in_buf = [0; 1024 * 64];
|
let mut in_buf = [0; 1024 * 64];
|
||||||
|
let mut error_count = 0;
|
||||||
|
|
||||||
for (mut reader, _) in files.iter().filter_map(|p| open(&p[..])) {
|
for file in files {
|
||||||
while let Ok(n) = reader.read(&mut in_buf) {
|
match open(&file[..]) {
|
||||||
|
Ok(mut handle) => {
|
||||||
|
while let Ok(n) = handle.reader.read(&mut in_buf) {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
writer.write_all(&in_buf[..n]).unwrap();
|
writer.write_all(&in_buf[..n]).context(&file[..])?;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
writeln!(&mut stderr(), "{}", error)?;
|
||||||
|
error_count += 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match error_count {
|
||||||
|
0 => Ok(()),
|
||||||
|
_ => Err(CatError::EncounteredErrors(error_count)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_lines(files: Vec<String>,
|
|
||||||
number: NumberingMode,
|
|
||||||
squeeze_blank: bool,
|
|
||||||
show_ends: bool,
|
|
||||||
show_tabs: bool,
|
|
||||||
show_nonprint: bool) {
|
|
||||||
// initialize end of line
|
|
||||||
let end_of_line = if show_ends {
|
|
||||||
"$\n".as_bytes()
|
|
||||||
} else {
|
|
||||||
"\n".as_bytes()
|
|
||||||
};
|
|
||||||
// initialize tab simbol
|
|
||||||
let tab = if show_tabs {
|
|
||||||
"^I".as_bytes()
|
|
||||||
} else {
|
|
||||||
"\t".as_bytes()
|
|
||||||
};
|
|
||||||
let mut line_counter: usize = 1;
|
|
||||||
|
|
||||||
for (mut reader, interactive) in files.iter().filter_map(|p| open(&p[..])) {
|
/// Writes files to stdout with `options` as configuration. Returns
|
||||||
|
/// `Ok(())` if no errors were encountered, or an error with the
|
||||||
|
/// number of errors encountered.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `files` - There is no short circuit when encountiner an error
|
||||||
|
/// reading a file in this vector
|
||||||
|
fn write_lines(files: Vec<String>, options: &OutputOptions) -> CatResult<()> {
|
||||||
|
let mut line_counter: usize = 1;
|
||||||
|
let mut error_count = 0;
|
||||||
|
|
||||||
|
for file in files {
|
||||||
|
let written = write_file_lines(&file[..], options, line_counter);
|
||||||
|
line_counter += match written {
|
||||||
|
Ok(lines) => lines,
|
||||||
|
Err(error) => {
|
||||||
|
writeln!(&mut stderr(), "{}", error).context(&file[..])?;
|
||||||
|
error_count += 1;
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match error_count {
|
||||||
|
0 => Ok(()),
|
||||||
|
_ => Err(CatError::EncounteredErrors(error_count)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outputs file and returns result with the number of lines to stdout
|
||||||
|
/// from `file`. If line numbering is enabled, then start output
|
||||||
|
/// numbering at `line_number`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `files` - There is no short circuit when encountiner an error
|
||||||
|
/// reading a file in this vector
|
||||||
|
fn write_file_lines(file: &str, options: &OutputOptions, line_number: usize) -> CatResult<usize> {
|
||||||
|
let mut handle = open(&file[..])?;
|
||||||
let mut in_buf = [0; 1024 * 31];
|
let mut in_buf = [0; 1024 * 31];
|
||||||
let mut writer = BufWriter::with_capacity(1024 * 64, stdout());
|
let mut writer = BufWriter::with_capacity(1024 * 64, stdout());
|
||||||
let mut at_line_start = true;
|
let mut at_line_start = true;
|
||||||
let mut one_blank_kept = false;
|
let mut one_blank_kept = false;
|
||||||
while let Ok(n) = reader.read(&mut in_buf) {
|
|
||||||
|
let mut lines = 0;
|
||||||
|
|
||||||
|
while let Ok(n) = handle.reader.read(&mut in_buf) {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let in_buf = &in_buf[..n];
|
let in_buf = &in_buf[..n];
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
while pos < n {
|
while pos < n {
|
||||||
// skip empty lines enumerating them if needed
|
// skip empty line_number enumerating them if needed
|
||||||
if in_buf[pos] == '\n' as u8 {
|
if in_buf[pos] == '\n' as u8 {
|
||||||
if !at_line_start || !squeeze_blank || !one_blank_kept {
|
if !at_line_start || ! options.squeeze_blank || !one_blank_kept {
|
||||||
one_blank_kept = true;
|
one_blank_kept = true;
|
||||||
if at_line_start && number == NumberingMode::NumberAll {
|
if at_line_start && options.number == NumberingMode::NumberAll {
|
||||||
(write!(&mut writer, "{0:6}\t", line_counter)).unwrap();
|
write!(&mut writer, "{0:6}\t", line_number + lines)?;
|
||||||
line_counter += 1;
|
lines += 1;
|
||||||
}
|
}
|
||||||
writer.write_all(end_of_line).unwrap();
|
writer.write_all(options.end_of_line.as_bytes())?;
|
||||||
if interactive {
|
if handle.is_interactive {
|
||||||
writer.flush().unwrap();
|
writer.flush().context(&file[..])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
at_line_start = true;
|
at_line_start = true;
|
||||||
|
@ -157,15 +365,15 @@ fn write_lines(files: Vec<String>,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
one_blank_kept = false;
|
one_blank_kept = false;
|
||||||
if at_line_start && number != NumberingMode::NumberNone {
|
if at_line_start && options.number != NumberingMode::NumberNone {
|
||||||
(write!(&mut writer, "{0:6}\t", line_counter)).unwrap();
|
write!(&mut writer, "{0:6}\t", line_number + lines)?;
|
||||||
line_counter += 1;
|
lines += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// print to end of line or end of buffer
|
// print to end of line or end of buffer
|
||||||
let offset = if show_nonprint {
|
let offset = if options.show_nonprint {
|
||||||
write_nonprint_to_end(&in_buf[pos..], &mut writer, tab)
|
write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab.as_bytes())
|
||||||
} else if show_tabs {
|
} else if options.show_tabs {
|
||||||
write_tab_to_end(&in_buf[pos..], &mut writer)
|
write_tab_to_end(&in_buf[pos..], &mut writer)
|
||||||
} else {
|
} else {
|
||||||
write_to_end(&in_buf[pos..], &mut writer)
|
write_to_end(&in_buf[pos..], &mut writer)
|
||||||
|
@ -176,15 +384,16 @@ fn write_lines(files: Vec<String>,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// print suitable end of line
|
// print suitable end of line
|
||||||
writer.write_all(end_of_line).unwrap();
|
writer.write_all(options.end_of_line.as_bytes())?;
|
||||||
if interactive {
|
if handle.is_interactive {
|
||||||
writer.flush().unwrap();
|
writer.flush()?;
|
||||||
}
|
}
|
||||||
at_line_start = true;
|
at_line_start = true;
|
||||||
pos += offset;
|
pos += offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Ok(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write***_to_end methods
|
// write***_to_end methods
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use common::util::*;
|
#[cfg(unix)]
|
||||||
|
extern crate unix_socket;
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
|
use common::util::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_output_multi_files_print_all_chars() {
|
fn test_output_multi_files_print_all_chars() {
|
||||||
|
@ -126,3 +129,30 @@ fn test_squeeze_blank_before_numbering() {
|
||||||
.stdout_only(" 1\ta\n 2\t\n 3\tb");
|
.stdout_only(" 1\ta\n 2\t\n 3\tb");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(foo)]
|
||||||
|
fn test_domain_socket() {
|
||||||
|
use std::thread;
|
||||||
|
use self::unix_socket::UnixListener;
|
||||||
|
use self::tempdir::TempDir;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
|
||||||
|
let dir = TempDir::new("unix_socket").expect("failed to create dir");
|
||||||
|
let socket_path = dir.path().join("sock");
|
||||||
|
let listener = UnixListener::bind(&socket_path).expect("failed to create socket");
|
||||||
|
|
||||||
|
let thread = thread::spawn(move || {
|
||||||
|
let mut stream = listener.accept().expect("failed to accept connection").0;
|
||||||
|
stream.write_all(b"a\tb").expect("failed to write test data");
|
||||||
|
});
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&[socket_path])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("a\tb");
|
||||||
|
|
||||||
|
thread.join().unwrap();
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue