From ad2d1d77939eab79d873611b4e238ab722c1e48c Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Mon, 4 Mar 2024 16:37:17 -0500 Subject: [PATCH] add util: mesg --- README.md | 2 +- TODO.md | 16 ++++++ users/Cargo.toml | 4 ++ users/src/mesg.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 TODO.md create mode 100644 users/src/mesg.rs diff --git a/README.md b/README.md index a33f1f8..ad2eb26 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ https://github.com/jgarzik/posixutils - [ ] mailx - [ ] make - [ ] man - - [ ] mesg + - [x] mesg - [ ] mkdir - [ ] mkfifo - [ ] more diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..1af598c --- /dev/null +++ b/TODO.md @@ -0,0 +1,16 @@ +# General TODO and future implementation notes + +## Translations + +* Standard OS error texts must be translated + +## OS errors + +* OS error messaging: many errors fail to conform to the standard of +``` + filename: OS error message +``` + +* `from_raw_os_error()` is probably pulling the wrong value and should + query errno + diff --git a/users/Cargo.toml b/users/Cargo.toml index 419b822..099256a 100644 --- a/users/Cargo.toml +++ b/users/Cargo.toml @@ -18,6 +18,10 @@ path = "src/logname.rs" name = "logger" path = "src/logger.rs" +[[bin]] +name = "mesg" +path = "src/mesg.rs" + [[bin]] name = "tty" path = "src/tty.rs" diff --git a/users/src/mesg.rs b/users/src/mesg.rs new file mode 100644 index 0000000..517f03a --- /dev/null +++ b/users/src/mesg.rs @@ -0,0 +1,129 @@ +// +// 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 +// +// TODO: +// - use .metadata() and std::os::unix::fs::PermissionsExt if possible +// - set process exit code according to spec +// + +extern crate clap; +extern crate libc; +extern crate plib; + +use clap::Parser; +use gettextrs::{bind_textdomain_codeset, gettext, textdomain}; +use plib::PROJECT_NAME; +use std::io::{self, Error, ErrorKind}; +use std::mem; + +/// mesg - permit or deny messages +#[derive(Parser, Debug)] +#[command(author, version, about, long_about)] +struct Args { + /// "y" or "n": Grant (y) or deny (n) permission to other users to send messages to the terminal device. + operand: Option, +} + +fn find_tty() -> Result { + let fds = vec![libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO]; + for fd in fds { + unsafe { + if libc::isatty(fd) > 0 { + return Ok(fd); + } + } + } + + Err("no tty found") +} + +fn stat_tty() -> io::Result<(i32, libc::stat)> { + let fd_res = find_tty(); + if let Err(e) = fd_res { + eprintln!("{}", gettext(e)); + return Err(Error::new(ErrorKind::Other, e)); + } + let fd = fd_res.unwrap(); + + unsafe { + let mut st: libc::stat = mem::zeroed(); + let ret = libc::fstat(fd, &mut st); + if ret < 0 { + eprintln!("{}", gettext("fstat failed")); + return Err(io::Error::from_raw_os_error(ret)); + } + + Ok((fd, st)) + } +} + +fn show_mesg(st: libc::stat) -> io::Result<()> { + if (st.st_mode & (libc::S_IWGRP | libc::S_IWOTH)) != 0 { + println!("is y"); + } else { + println!("is n"); + } + Ok(()) +} + +fn parse_setting(setting: &str) -> Result { + match setting { + "y" | "Y" => Ok(true), + "n" | "N" => Ok(false), + _ => Err("invalid operand"), + } +} + +fn set_mesg(fd: i32, st: libc::stat, setting: &str) -> io::Result<()> { + let res = parse_setting(setting); + if let Err(e) = res { + return Err(Error::new(ErrorKind::Other, e)); + } + let affirm = res.unwrap(); + + let mut mode = st.st_mode; + + if affirm { + if (mode & (libc::S_IWGRP | libc::S_IWOTH)) != 0 { + return Ok(()); + } + + mode = mode | libc::S_IWGRP | libc::S_IWOTH; + } else { + if (mode & (libc::S_IWGRP | libc::S_IWOTH)) == 0 { + return Ok(()); + } + + mode = mode & !(libc::S_IWGRP | libc::S_IWOTH); + } + + let chres = unsafe { libc::fchmod(fd, mode) }; + if chres < 0 { + eprintln!("{}", gettext("failed to change terminal mode")); + return Err(io::Error::from_raw_os_error(chres)); + } + + Ok(()) +} + +fn main() -> Result<(), Box> { + // parse command line arguments + let args = Args::parse(); + + textdomain(PROJECT_NAME)?; + bind_textdomain_codeset(PROJECT_NAME, "UTF-8")?; + + let (fd, stat) = stat_tty()?; + + match args.operand { + None => show_mesg(stat)?, + Some(op) => set_mesg(fd, stat, &op)?, + } + + Ok(()) +}