-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
318 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,313 @@ | ||
// | ||
// Copyright (c) 2024 Jeff Garzik | ||
// | ||
// This file is part of the posixutils-rs project covered under | ||
// the MIT License. For the full license text, please see the LICENSE | ||
// file in the root directory of this project. | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
extern crate plib; | ||
|
||
use gettextrs::{bind_textdomain_codeset, gettext, textdomain}; | ||
use plib::PROJECT_NAME; | ||
use std::fs; | ||
use std::io::{self, Read, Write}; | ||
|
||
const DEF_BLOCK_SIZE: usize = 512; | ||
|
||
const CONV_ASCII_IBM: [u8; 256] = [ | ||
0x0, 0x1, 0x2, 0x3, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x5, 0x25, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, | ||
0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f, 0x40, | ||
0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61, 0xf0, | ||
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f, 0x7c, | ||
0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, | ||
0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xad, 0xe0, 0xbd, 0x5f, 0x6d, 0x79, | ||
0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, | ||
0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0xa1, 0x7, 0x20, | ||
0x21, 0x22, 0x23, 0x24, 0x15, 0x6, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x9, 0xa, 0x1b, 0x30, | ||
0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x8, 0x38, 0x39, 0x3a, 0x3b, 0x4, 0x14, 0x3e, 0xe1, 0x41, | ||
0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, | ||
0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, | ||
0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, | ||
0xa0, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, | ||
0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb, 0xdc, | ||
0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, | ||
]; | ||
|
||
const CONV_EBCDIC_ASCII: [u8; 256] = [ | ||
0x0, 0x1, 0x2, 0x3, 0x9c, 0x9, 0x86, 0x7f, 0x97, 0x8d, 0x8e, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, | ||
0x11, 0x12, 0x13, 0x9d, 0x85, 0x8, 0x87, 0x18, 0x19, 0x92, 0x8f, 0x1c, 0x1d, 0x1e, 0x1f, 0x80, | ||
0x81, 0x82, 0x83, 0x84, 0xa, 0x17, 0x1b, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x5, 0x6, 0x7, 0x90, | ||
0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x4, 0x98, 0x99, 0x9a, 0x9b, 0x14, 0x15, 0x9e, 0x1a, 0x20, | ||
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xd5, 0x2e, 0x3c, 0x28, 0x2b, 0x7c, 0x26, | ||
0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0x21, 0x24, 0x2a, 0x29, 0x3b, 0x7e, 0x2d, | ||
0x2f, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xcb, 0x2c, 0x25, 0x5f, 0x3e, 0x3f, 0xba, | ||
0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0x60, 0x3a, 0x23, 0x40, 0x27, 0x3d, 0x22, 0xc3, | ||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, | ||
0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x5e, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, | ||
0xe5, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0xd2, 0xd3, 0xd4, 0x5b, 0xd6, 0xd7, 0xd8, | ||
0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0x5d, 0xe6, 0xe7, 0x7b, | ||
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0x7d, | ||
0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0x5c, | ||
0x9f, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x30, | ||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, | ||
]; | ||
|
||
const CONV_ASCII_EBCDIC: [u8; 256] = [ | ||
0x0, 0x1, 0x2, 0x3, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x5, 0x25, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, | ||
0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f, 0x40, | ||
0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61, 0xf0, | ||
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f, 0x7c, | ||
0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, | ||
0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xad, 0xe0, 0xbd, 0x9a, 0x6d, 0x79, | ||
0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, | ||
0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0x5f, 0x7, 0x20, | ||
0x21, 0x22, 0x23, 0x24, 0x15, 0x6, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x9, 0xa, 0x1b, 0x30, | ||
0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x8, 0x38, 0x39, 0x3a, 0x3b, 0x4, 0x14, 0x3e, 0xe1, 0x41, | ||
0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, | ||
0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, | ||
0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x6a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, | ||
0xa0, 0xaa, 0xab, 0xac, 0x4a, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, | ||
0xb9, 0xba, 0xbb, 0xbc, 0xa1, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb, 0xdc, | ||
0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, | ||
]; | ||
|
||
#[derive(Debug)] | ||
enum AsciiConv { | ||
Ascii, | ||
EBCDIC, | ||
IBM, | ||
} | ||
|
||
#[derive(Debug)] | ||
struct Config { | ||
ifile: String, | ||
ofile: String, | ||
bs: usize, | ||
ibs: usize, | ||
obs: usize, | ||
cbs: usize, | ||
seek: usize, | ||
skip: usize, | ||
count: usize, | ||
|
||
ascii: Option<AsciiConv>, | ||
block: Option<bool>, | ||
lcase: bool, | ||
ucase: bool, | ||
swab: bool, | ||
noerror: bool, | ||
notrunc: bool, | ||
sync: bool, | ||
} | ||
|
||
impl Config { | ||
fn new() -> Config { | ||
Config { | ||
ifile: String::from("-"), | ||
ofile: String::from("-"), | ||
bs: DEF_BLOCK_SIZE, | ||
ibs: DEF_BLOCK_SIZE, | ||
obs: DEF_BLOCK_SIZE, | ||
cbs: 0, | ||
seek: 0, | ||
skip: 0, | ||
count: 0, | ||
ascii: None, | ||
block: None, | ||
lcase: false, | ||
ucase: false, | ||
swab: false, | ||
noerror: false, | ||
notrunc: false, | ||
sync: false, | ||
} | ||
} | ||
} | ||
|
||
fn parse_conv_list(config: &mut Config, s: &str) -> Result<(), Box<dyn std::error::Error>> { | ||
for convstr in s.split(",") { | ||
match convstr { | ||
"ascii" => config.ascii = Some(AsciiConv::Ascii), | ||
"ebcdic" => config.ascii = Some(AsciiConv::EBCDIC), | ||
"ibm" => config.ascii = Some(AsciiConv::IBM), | ||
"block" => config.block = Some(true), | ||
"unblock" => config.block = Some(false), | ||
"lcase" => config.lcase = true, | ||
"ucase" => config.ucase = true, | ||
"swab" => config.swab = true, | ||
"noerror" => config.noerror = true, | ||
"notrunc" => config.notrunc = true, | ||
"sync" => config.sync = true, | ||
_ => { | ||
eprintln!("{}: {}", gettext("invalid conv option"), convstr); | ||
return Err("invalid conv option".into()); | ||
} | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
fn parse_block_size(s: &str) -> Result<usize, Box<dyn std::error::Error>> { | ||
let mut s = s.to_string(); | ||
let mut scale = 1; | ||
let suffix = s.pop().unwrap(); | ||
if suffix.is_alphabetic() { | ||
match suffix { | ||
'c' => scale = 1, | ||
'w' => scale = 2, | ||
'b' => scale = 512, | ||
'k' | 'K' => scale = 1024, | ||
'm' | 'M' => scale = 1024 * 1024, | ||
'g' | 'G' => scale = 1024 * 1024 * 1024, | ||
_ => { | ||
eprintln!("{}: {}", gettext("invalid block size suffix"), suffix); | ||
return Err("invalid block size suffix".into()); | ||
} | ||
} | ||
} else { | ||
s.push(suffix); | ||
} | ||
let size = s.parse::<usize>()?; | ||
Ok(size * scale) | ||
} | ||
|
||
fn parse_cmdline(args: &[String]) -> Result<Config, Box<dyn std::error::Error>> { | ||
let mut config = Config::new(); | ||
|
||
for arg in args { | ||
// Split arg into option and argument | ||
let (op, oparg) = { | ||
match arg.split_once("=") { | ||
None => { | ||
let msg = format!("{}: {}", gettext("invalid option"), arg); | ||
eprintln!("{}", msg); | ||
return Err(msg.into()); | ||
} | ||
Some((opt, optarg)) => (opt, optarg.to_string()), | ||
} | ||
}; | ||
|
||
// per-option processing | ||
match op { | ||
"if" => config.ifile = oparg, | ||
"of" => config.ofile = oparg, | ||
"ibs" => config.ibs = parse_block_size(&oparg)?, | ||
"obs" => config.obs = parse_block_size(&oparg)?, | ||
"bs" => { | ||
config.bs = parse_block_size(&oparg)?; | ||
config.ibs = config.bs; | ||
config.obs = config.bs; | ||
} | ||
"cbs" => config.cbs = parse_block_size(&oparg)?, | ||
"skip" => config.skip = oparg.parse::<usize>()?, | ||
"seek" => config.seek = oparg.parse::<usize>()?, | ||
"count" => config.count = oparg.parse::<usize>()?, | ||
"conv" => parse_conv_list(&mut config, &oparg)?, | ||
|
||
_ => { | ||
eprintln!("{}: {}", gettext("invalid option"), op); | ||
} | ||
} | ||
} | ||
Ok(config) | ||
} | ||
|
||
fn copy_convert_file(config: &Config) -> Result<(), Box<dyn std::error::Error>> { | ||
let mut ifile: Box<dyn Read>; | ||
if config.ifile == "-" { | ||
ifile = Box::new(io::stdin().lock()); | ||
} else { | ||
ifile = Box::new(fs::File::open(&config.ifile)?); | ||
} | ||
let mut ofile: Box<dyn Write>; | ||
if config.ofile == "-" { | ||
ofile = Box::new(io::stdout().lock()) | ||
} else { | ||
ofile = Box::new(fs::File::create(&config.ofile)?) | ||
} | ||
|
||
let mut ibuf = vec![0u8; config.ibs]; | ||
let mut obuf = vec![0u8; config.obs]; | ||
|
||
let mut count = 0; | ||
let mut skip = config.skip; | ||
let mut seek = config.seek; | ||
|
||
loop { | ||
if skip > 0 { | ||
let n = ifile.read(&mut ibuf)?; | ||
if n == 0 { | ||
break; | ||
} | ||
skip -= n; | ||
continue; | ||
} | ||
|
||
if seek > 0 { | ||
let n = ifile.read(&mut ibuf)?; | ||
if n == 0 { | ||
break; | ||
} | ||
seek -= n; | ||
continue; | ||
} | ||
|
||
let n = ifile.read(&mut ibuf)?; | ||
if n == 0 { | ||
break; | ||
} | ||
|
||
if config.count > 0 { | ||
if count >= config.count { | ||
break; | ||
} | ||
count += 1; | ||
} | ||
|
||
let ibuf = &ibuf[..n]; | ||
let obuf = &mut obuf[..n]; | ||
|
||
if let Some(ascii) = &config.ascii { | ||
match ascii { | ||
AsciiConv::Ascii => { | ||
// convert EBCDIC to ASCII | ||
for i in 0..n { | ||
obuf[i] = CONV_EBCDIC_ASCII[ibuf[i] as usize]; | ||
} | ||
} | ||
AsciiConv::EBCDIC => { | ||
// convert ASCII to EBCDIC | ||
for i in 0..n { | ||
obuf[i] = CONV_ASCII_EBCDIC[ibuf[i] as usize]; | ||
} | ||
} | ||
AsciiConv::IBM => { | ||
// convert ASCII to IBM | ||
for i in 0..n { | ||
obuf[i] = CONV_ASCII_IBM[ibuf[i] as usize]; | ||
} | ||
} | ||
} | ||
} else { | ||
obuf.copy_from_slice(ibuf); | ||
} | ||
|
||
ofile.write(&obuf)?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
textdomain(PROJECT_NAME)?; | ||
bind_textdomain_codeset(PROJECT_NAME, "UTF-8")?; | ||
|
||
let args: Vec<String> = std::env::args().skip(1).collect(); | ||
let config = parse_cmdline(&args)?; | ||
|
||
copy_convert_file(&config)?; | ||
|
||
Ok(()) | ||
} |