From 4cda8f33e70d68688045ae816b25c2f2f21e50e8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 27 Jun 2022 13:00:31 +0200 Subject: [PATCH] stty: add grouped flags --- src/uu/stty/src/flags.rs | 391 ++++++++------------------------------- src/uu/stty/src/stty.rs | 226 +++++++++++++--------- 2 files changed, 218 insertions(+), 399 deletions(-) diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index b2688cb8d..7a985a04b 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -3,330 +3,95 @@ // * For the full copyright and license information, please view the LICENSE file // * that was distributed with this source code. -// spell-checker:ignore parenb parodd cmspar hupcl cstopb cread clocal crtscts +// spell-checker:ignore parenb parodd cmspar hupcl cstopb cread clocal crtscts CSIZE // spell-checker:ignore ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl ixoff ixon iuclc ixany imaxbel iutf -// spell-checker:ignore opost olcuc ocrnl onlcr onocr onlret ofill ofdel +// spell-checker:ignore opost olcuc ocrnl onlcr onocr onlret ofill ofdel nldly crdly tabdly bsdly vtdly ffdly // spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc use crate::Flag; -use nix::sys::termios::{ControlFlags, InputFlags, LocalFlags, OutputFlags}; +use nix::sys::termios::{ControlFlags as C, InputFlags as I, LocalFlags as L, OutputFlags as O}; -pub const CONTROL_FLAGS: [Flag; 8] = [ - Flag { - name: "parenb", - flag: ControlFlags::PARENB, - show: true, - sane: false, - }, - Flag { - name: "parodd", - flag: ControlFlags::PARODD, - show: true, - sane: false, - }, - Flag { - name: "cmspar", - flag: ControlFlags::CMSPAR, - show: true, - sane: false, - }, - Flag { - name: "hupcl", - flag: ControlFlags::HUPCL, - show: true, - sane: true, - }, - Flag { - name: "cstopb", - flag: ControlFlags::CSTOPB, - show: true, - sane: false, - }, - Flag { - name: "cread", - flag: ControlFlags::CREAD, - show: true, - sane: true, - }, - Flag { - name: "clocal", - flag: ControlFlags::CLOCAL, - show: true, - sane: false, - }, - Flag { - name: "crtscts", - flag: ControlFlags::CRTSCTS, - show: true, - sane: false, - }, +pub const CONTROL_FLAGS: [Flag; 12] = [ + Flag::new("parenb", C::PARENB), + Flag::new("parodd", C::PARODD), + Flag::new("cmspar", C::CMSPAR), + Flag::new("cs5", C::CS5).group(C::CSIZE), + Flag::new("cs6", C::CS6).group(C::CSIZE), + Flag::new("cs7", C::CS7).group(C::CSIZE), + Flag::new("cs8", C::CS8).group(C::CSIZE).sane(), + Flag::new("hupcl", C::HUPCL).sane(), + Flag::new("cstopb", C::CSTOPB), + Flag::new("cread", C::CREAD).sane(), + Flag::new("clocal", C::CLOCAL), + Flag::new("crtscts", C::CRTSCTS), ]; -pub const INPUT_FLAGS: [Flag; 15] = [ - Flag { - name: "ignbrk", - flag: InputFlags::IGNBRK, - show: true, - sane: false, - }, - Flag { - name: "brkint", - flag: InputFlags::BRKINT, - show: true, - sane: true, - }, - Flag { - name: "ignpar", - flag: InputFlags::IGNPAR, - show: true, - sane: false, - }, - Flag { - name: "parmrk", - flag: InputFlags::PARMRK, - show: true, - sane: false, - }, - Flag { - name: "inpck", - flag: InputFlags::INPCK, - show: true, - sane: false, - }, - Flag { - name: "istrip", - flag: InputFlags::ISTRIP, - show: true, - sane: false, - }, - Flag { - name: "inlcr", - flag: InputFlags::INLCR, - show: true, - sane: false, - }, - Flag { - name: "igncr", - flag: InputFlags::IGNCR, - show: true, - sane: false, - }, - Flag { - name: "icrnl", - flag: InputFlags::ICRNL, - show: true, - sane: true, - }, - Flag { - name: "ixoff", - flag: InputFlags::IXOFF, - show: true, - sane: false, - }, - Flag { - name: "tandem", - flag: InputFlags::IXOFF, - show: false, - sane: false, - }, - Flag { - name: "ixon", - flag: InputFlags::IXON, - show: true, - sane: false, - }, +pub const INPUT_FLAGS: [Flag; 15] = [ + Flag::new("ignbrk", I::IGNBRK), + Flag::new("brkint", I::BRKINT).sane(), + Flag::new("ignpar", I::IGNPAR), + Flag::new("parmrk", I::PARMRK), + Flag::new("inpck", I::INPCK), + Flag::new("istrip", I::ISTRIP), + Flag::new("inlcr", I::INLCR), + Flag::new("igncr", I::IGNCR), + Flag::new("icrnl", I::ICRNL).sane(), + Flag::new("ixoff", I::IXOFF), + Flag::new("tandem", I::IXOFF), + Flag::new("ixon", I::IXON), // not supported by nix - // Flag { - // name: "iuclc", - // flag: InputFlags::IUCLC, - // show: true, - // default: false, - // }, - Flag { - name: "ixany", - flag: InputFlags::IXANY, - show: true, - sane: false, - }, - Flag { - name: "imaxbel", - flag: InputFlags::IMAXBEL, - show: true, - sane: true, - }, - Flag { - name: "iutf8", - flag: InputFlags::IUTF8, - show: true, - sane: false, - }, + // Flag::new("iuclc", I::IUCLC), + Flag::new("ixany", I::IXANY).sane(), + Flag::new("imaxbel", I::IMAXBEL).sane(), + Flag::new("iutf8", I::IUTF8).sane(), ]; -pub const OUTPUT_FLAGS: [Flag; 8] = [ - Flag { - name: "opost", - flag: OutputFlags::OPOST, - show: true, - sane: true, - }, - Flag { - name: "olcuc", - flag: OutputFlags::OLCUC, - show: true, - sane: false, - }, - Flag { - name: "ocrnl", - flag: OutputFlags::OCRNL, - show: true, - sane: false, - }, - Flag { - name: "onlcr", - flag: OutputFlags::ONLCR, - show: true, - sane: true, - }, - Flag { - name: "onocr", - flag: OutputFlags::ONOCR, - show: true, - sane: false, - }, - Flag { - name: "onlret", - flag: OutputFlags::ONLRET, - show: true, - sane: false, - }, - Flag { - name: "ofill", - flag: OutputFlags::OFILL, - show: true, - sane: false, - }, - Flag { - name: "ofdel", - flag: OutputFlags::OFDEL, - show: true, - sane: false, - }, +pub const OUTPUT_FLAGS: [Flag; 24] = [ + Flag::new("opost", O::OPOST).sane(), + Flag::new("olcuc", O::OLCUC), + Flag::new("ocrnl", O::OCRNL), + Flag::new("onlcr", O::ONLCR).sane(), + Flag::new("onocr", O::ONOCR), + Flag::new("onlret", O::ONLRET), + Flag::new("ofill", O::OFILL), + Flag::new("ofdel", O::OFDEL), + Flag::new("nl0", O::NL0).group(O::NLDLY).sane(), + Flag::new("nl1", O::NL1).group(O::NLDLY), + Flag::new("cr0", O::CR0).group(O::CRDLY).sane(), + Flag::new("cr1", O::CR1).group(O::CRDLY), + Flag::new("cr2", O::CR2).group(O::CRDLY), + Flag::new("cr3", O::CR3).group(O::CRDLY), + Flag::new("tab0", O::TAB0).group(O::TABDLY).sane(), + Flag::new("tab1", O::TAB1).group(O::TABDLY), + Flag::new("tab2", O::TAB2).group(O::TABDLY), + Flag::new("tab3", O::TAB3).group(O::TABDLY), + Flag::new("bs0", O::BS0).group(O::BSDLY).sane(), + Flag::new("bs1", O::BS1).group(O::BSDLY), + Flag::new("vt0", O::VT0).group(O::VTDLY).sane(), + Flag::new("vt1", O::VT1).group(O::VTDLY), + Flag::new("ff0", O::FF0).group(O::FFDLY).sane(), + Flag::new("ff1", O::FF1).group(O::FFDLY), ]; -pub const LOCAL_FLAGS: [Flag; 18] = [ - Flag { - name: "isig", - flag: LocalFlags::ISIG, - show: true, - sane: true, - }, - Flag { - name: "icanon", - flag: LocalFlags::ICANON, - show: true, - sane: true, - }, - Flag { - name: "iexten", - flag: LocalFlags::IEXTEN, - show: true, - sane: true, - }, - Flag { - name: "echo", - flag: LocalFlags::ECHO, - show: true, - sane: true, - }, - Flag { - name: "echoe", - flag: LocalFlags::ECHOE, - show: true, - sane: true, - }, - Flag { - name: "crterase", - flag: LocalFlags::ECHOE, - show: false, - sane: true, - }, - Flag { - name: "echok", - flag: LocalFlags::ECHOK, - show: true, - sane: true, - }, - Flag { - name: "echonl", - flag: LocalFlags::ECHONL, - show: true, - sane: false, - }, - Flag { - name: "noflsh", - flag: LocalFlags::NOFLSH, - show: true, - sane: false, - }, +pub const LOCAL_FLAGS: [Flag; 18] = [ + Flag::new("isig", L::ISIG).sane(), + Flag::new("icanon", L::ICANON).sane(), + Flag::new("iexten", L::IEXTEN).sane(), + Flag::new("echo", L::ECHO).sane(), + Flag::new("echoe", L::ECHOE).sane(), + Flag::new("crterase", L::ECHOE).hidden().sane(), + Flag::new("echok", L::ECHOK).sane(), + Flag::new("echonl", L::ECHONL), + Flag::new("noflsh", L::NOFLSH), // Not supported by nix - // Flag { - // name: "xcase", - // flag: LocalFlags::XCASE, - // show: true, - // sane: false, - // }, - Flag { - name: "tostop", - flag: LocalFlags::TOSTOP, - show: true, - sane: false, - }, - Flag { - name: "echoprt", - flag: LocalFlags::ECHOPRT, - show: true, - sane: false, - }, - Flag { - name: "prterase", - flag: LocalFlags::ECHOPRT, - show: false, - sane: false, - }, - Flag { - name: "echoctl", - flag: LocalFlags::ECHOCTL, - show: true, - sane: true, - }, - Flag { - name: "ctlecho", - flag: LocalFlags::ECHOCTL, - show: false, - sane: true, - }, - Flag { - name: "echoke", - flag: LocalFlags::ECHOKE, - show: true, - sane: true, - }, - Flag { - name: "crtkill", - flag: LocalFlags::ECHOKE, - show: false, - sane: true, - }, - Flag { - name: "flusho", - flag: LocalFlags::FLUSHO, - show: true, - sane: false, - }, - Flag { - name: "extproc", - flag: LocalFlags::EXTPROC, - show: true, - sane: false, - }, + // Flag::new("xcase", L::XCASE), + Flag::new("tostop", L::TOSTOP), + Flag::new("echoprt", L::ECHOPRT), + Flag::new("prterase", L::ECHOPRT).hidden(), + Flag::new("echoctl", L::ECHOCTL).sane(), + Flag::new("ctlecho", L::ECHOCTL).sane().hidden(), + Flag::new("echoke", L::ECHOKE).sane(), + Flag::new("crtkill", L::ECHOKE).sane().hidden(), + Flag::new("flusho", L::FLUSHO), + Flag::new("extproc", L::EXTPROC), ]; diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 52cd5995b..fa29f761b 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -12,8 +12,9 @@ use nix::sys::termios::{ tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios, }; use std::io::{self, stdout}; +use std::ops::ControlFlow; use std::os::unix::io::{AsRawFd, RawFd}; -use uucore::error::UResult; +use uucore::error::{UResult, USimpleError}; use uucore::{format_usage, InvalidEncodingHandling}; use flags::{CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS}; @@ -25,15 +26,53 @@ const USAGE: &str = "\ {} [-F DEVICE | --file=DEVICE] [-g|--save]"; const SUMMARY: &str = "Print or change terminal characteristics."; +#[derive(Clone, Copy, Debug)] pub struct Flag { name: &'static str, flag: T, show: bool, sane: bool, + group: Option, } -trait TermiosFlag { - fn is_in(&self, termios: &Termios) -> bool; +impl Flag +where + T: Copy, +{ + pub const fn new(name: &'static str, flag: T) -> Self { + Flag { + name, + flag, + show: true, + sane: false, + group: None, + } + } + + pub const fn hidden(&self) -> Self { + Self { + show: false, + ..*self + } + } + + pub const fn sane(&self) -> Self { + Self { + sane: true, + ..*self + } + } + + pub const fn group(&self, group: T) -> Self { + Self { + group: Some(group), + ..*self + } + } +} + +trait TermiosFlag: Copy { + fn is_in(&self, termios: &Termios, group: Option) -> bool; fn apply(&self, termios: &mut Termios, val: bool); } @@ -83,7 +122,12 @@ fn stty(opts: &Options) -> UResult<()> { let mut termios = tcgetattr(opts.file).expect("Could not get terminal attributes"); if let Some(settings) = &opts.settings { for setting in settings { - apply_setting(&mut termios, setting); + if let ControlFlow::Break(false) = apply_setting(&mut termios, setting) { + return Err(USimpleError::new( + 1, + format!("invalid argument '{}'", setting), + )); + } } tcsetattr(opts.file, nix::sys::termios::SetArg::TCSANOW, &termios) @@ -95,72 +139,91 @@ fn stty(opts: &Options) -> UResult<()> { } fn print_settings(termios: &Termios, opts: &Options) { - if print_flags(termios, opts, &CONTROL_FLAGS) { - println!(); - } - if print_flags(termios, opts, &INPUT_FLAGS) { - println!(); - } - if print_flags(termios, opts, &OUTPUT_FLAGS) { - println!(); - } - if print_flags(termios, opts, &LOCAL_FLAGS) { - println!(); - } + print_flags(termios, opts, &CONTROL_FLAGS); + print_flags(termios, opts, &INPUT_FLAGS); + print_flags(termios, opts, &OUTPUT_FLAGS); + print_flags(termios, opts, &LOCAL_FLAGS); } -fn print_flags(termios: &Termios, opts: &Options, flags: &[Flag]) -> bool -where - Flag: TermiosFlag, -{ +fn print_flags(termios: &Termios, opts: &Options, flags: &[Flag]) { let mut printed = false; - for flag in flags { - if !flag.show { + for &Flag { + name, + flag, + show, + sane, + group, + } in flags + { + if !show { continue; } - let val = flag.is_in(termios); - if opts.all || val != flag.sane { - if !val { - print!("-"); + let val = flag.is_in(termios, group); + if group.is_some() { + if val && (!sane || opts.all) { + print!("{name} "); + printed = true; + } + } else { + if opts.all || val != sane { + if !val { + print!("-"); + } + print!("{name} "); + printed = true; } - print!("{} ", flag.name); - printed = true; } } - printed + if printed { + println!(); + } } -fn apply_setting(termios: &mut Termios, s: &str) -> Option<()> { - if let Some(()) = apply_flag(termios, &CONTROL_FLAGS, s) { - return Some(()); - } - if let Some(()) = apply_flag(termios, &INPUT_FLAGS, s) { - return Some(()); - } - if let Some(()) = apply_flag(termios, &OUTPUT_FLAGS, s) { - return Some(()); - } - if let Some(()) = apply_flag(termios, &LOCAL_FLAGS, s) { - return Some(()); - } - None -} - -fn apply_flag(termios: &mut Termios, flags: &[Flag], name: &str) -> Option<()> -where - T: Copy, - Flag: TermiosFlag, -{ - let (remove, name) = strip_hyphen(name); - find(flags, name)?.apply(termios, !remove); - Some(()) -} - -fn strip_hyphen(s: &str) -> (bool, &str) { - match s.strip_prefix('-') { +/// Apply a single setting +/// +/// The value inside the `Break` variant of the `ControlFlow` indicates whether +/// the setting has been applied. +fn apply_setting(termios: &mut Termios, s: &str) -> ControlFlow { + let (remove, name) = match s.strip_prefix('-') { Some(s) => (true, s), None => (false, s), + }; + apply_flag(termios, &CONTROL_FLAGS, name, remove)?; + apply_flag(termios, &INPUT_FLAGS, name, remove)?; + apply_flag(termios, &OUTPUT_FLAGS, name, remove)?; + apply_flag(termios, &LOCAL_FLAGS, name, remove)?; + ControlFlow::Break(false) +} + +/// Apply a flag to a slice of flags +/// +/// The value inside the `Break` variant of the `ControlFlow` indicates whether +/// the setting has been applied. +fn apply_flag( + termios: &mut Termios, + flags: &[Flag], + input: &str, + remove: bool, +) -> ControlFlow { + for Flag { + name, flag, group, .. + } in flags + { + if input == *name { + // Flags with groups cannot be removed + // Since the name matches, we can short circuit and don't have to check the other flags. + if remove || group.is_some() { + return ControlFlow::Break(false); + } + // If there is a group, the bits for that group should be cleared before applying the flag + if let Some(group) = group { + group.apply(termios, false); + } + flag.apply(termios, !remove); + return ControlFlow::Break(true); + } } + ControlFlow::Continue(()) } pub fn uu_app<'a>() -> Command<'a> { @@ -186,55 +249,46 @@ pub fn uu_app<'a>() -> Command<'a> { ) } -impl TermiosFlag for Flag { - fn is_in(&self, termios: &Termios) -> bool { - termios.control_flags.contains(self.flag) +impl TermiosFlag for ControlFlags { + fn is_in(&self, termios: &Termios, group: Option) -> bool { + termios.control_flags.contains(*self) + && group.map_or(true, |g| !termios.control_flags.intersects(g - *self)) } fn apply(&self, termios: &mut Termios, val: bool) { - termios.control_flags.set(self.flag, val) + termios.control_flags.set(*self, val) } } -impl TermiosFlag for Flag { - fn is_in(&self, termios: &Termios) -> bool { - termios.input_flags.contains(self.flag) +impl TermiosFlag for InputFlags { + fn is_in(&self, termios: &Termios, group: Option) -> bool { + termios.input_flags.contains(*self) + && group.map_or(true, |g| !termios.input_flags.intersects(g - *self)) } fn apply(&self, termios: &mut Termios, val: bool) { - termios.input_flags.set(self.flag, val) + termios.input_flags.set(*self, val) } } -impl TermiosFlag for Flag { - fn is_in(&self, termios: &Termios) -> bool { - termios.output_flags.contains(self.flag) +impl TermiosFlag for OutputFlags { + fn is_in(&self, termios: &Termios, group: Option) -> bool { + termios.output_flags.contains(*self) + && group.map_or(true, |g| !termios.output_flags.intersects(g - *self)) } fn apply(&self, termios: &mut Termios, val: bool) { - termios.output_flags.set(self.flag, val) + termios.output_flags.set(*self, val) } } -impl TermiosFlag for Flag { - fn is_in(&self, termios: &Termios) -> bool { - termios.local_flags.contains(self.flag) +impl TermiosFlag for LocalFlags { + fn is_in(&self, termios: &Termios, group: Option) -> bool { + termios.local_flags.contains(*self) + && group.map_or(true, |g| !termios.local_flags.intersects(g - *self)) } fn apply(&self, termios: &mut Termios, val: bool) { - termios.local_flags.set(self.flag, val) + termios.local_flags.set(*self, val) } } - -fn find<'a, T>(flags: &'a [Flag], flag_name: &str) -> Option<&'a Flag> -where - T: Copy, -{ - flags.iter().find_map(|flag| { - if flag.name == flag_name { - Some(flag) - } else { - None - } - }) -}