From c8aa5a6b578c1149431661236724fb1c1d7b94ef Mon Sep 17 00:00:00 2001
From: Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
Date: Sat, 1 Mar 2025 22:38:19 +0200
Subject: [PATCH] mesg: Add tool

---
 Cargo.lock                 |  10 ++++
 Cargo.toml                 |   2 +
 src/uu/mesg/Cargo.toml     |  16 ++++++
 src/uu/mesg/mesg.md        |   7 +++
 src/uu/mesg/src/main.rs    |   1 +
 src/uu/mesg/src/mesg.rs    | 101 +++++++++++++++++++++++++++++++++++++
 tests/by-util/test_mesg.rs |  36 +++++++++++++
 tests/tests.rs             |   4 ++
 8 files changed, 177 insertions(+)
 create mode 100644 src/uu/mesg/Cargo.toml
 create mode 100644 src/uu/mesg/mesg.md
 create mode 100644 src/uu/mesg/src/main.rs
 create mode 100644 src/uu/mesg/src/mesg.rs
 create mode 100644 tests/by-util/test_mesg.rs

diff --git a/Cargo.lock b/Cargo.lock
index 8ff316a..e447cdc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -976,6 +976,7 @@ dependencies = [
  "uu_lscpu",
  "uu_lslocks",
  "uu_lsmem",
+ "uu_mesg",
  "uu_mountpoint",
  "uu_rev",
  "uu_setsid",
@@ -1067,6 +1068,15 @@ dependencies = [
  "uucore",
 ]
 
+[[package]]
+name = "uu_mesg"
+version = "0.0.1"
+dependencies = [
+ "clap",
+ "nix",
+ "uucore",
+]
+
 [[package]]
 name = "uu_mountpoint"
 version = "0.0.1"
diff --git a/Cargo.toml b/Cargo.toml
index 2c7adc1..e4242c5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,6 +34,7 @@ feat_common_core = [
   "lscpu",
   "lslocks",
   "lsmem",
+  "mesg",
   "mountpoint",
   "rev",
   "setsid",
@@ -79,6 +80,7 @@ last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu
 lscpu = { optional = true, version = "0.0.1", package = "uu_lscpu", path = "src/uu/lscpu" }
 lslocks = { optional = true, version = "0.0.1", package = "uu_lslocks", path = "src/uu/lslocks" }
 lsmem = { optional = true, version = "0.0.1", package = "uu_lsmem", path = "src/uu/lsmem" }
+mesg = { optional = true, version = "0.0.1", package = "uu_mesg", path = "src/uu/mesg" }
 mountpoint = { optional = true, version = "0.0.1", package = "uu_mountpoint", path = "src/uu/mountpoint" }
 rev = { optional = true, version = "0.0.1", package = "uu_rev", path = "src/uu/rev" }
 setsid = { optional = true, version = "0.0.1", package = "uu_setsid", path ="src/uu/setsid" }
diff --git a/src/uu/mesg/Cargo.toml b/src/uu/mesg/Cargo.toml
new file mode 100644
index 0000000..c75a4d8
--- /dev/null
+++ b/src/uu/mesg/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "uu_mesg"
+version = "0.0.1"
+edition = "2021"
+
+[lib]
+path = "src/mesg.rs"
+
+[[bin]]
+name = "mesg"
+path = "src/main.rs"
+
+[dependencies]
+clap = { workspace = true }
+nix = { workspace = true }
+uucore = { workspace = true }
diff --git a/src/uu/mesg/mesg.md b/src/uu/mesg/mesg.md
new file mode 100644
index 0000000..afcb543
--- /dev/null
+++ b/src/uu/mesg/mesg.md
@@ -0,0 +1,7 @@
+# mesg
+
+```
+mesg [option] [y|n]
+```
+
+enables or disables displaying messages from other users
diff --git a/src/uu/mesg/src/main.rs b/src/uu/mesg/src/main.rs
new file mode 100644
index 0000000..aff7ec1
--- /dev/null
+++ b/src/uu/mesg/src/main.rs
@@ -0,0 +1 @@
+uucore::bin!(uu_mesg);
diff --git a/src/uu/mesg/src/mesg.rs b/src/uu/mesg/src/mesg.rs
new file mode 100644
index 0000000..220ed36
--- /dev/null
+++ b/src/uu/mesg/src/mesg.rs
@@ -0,0 +1,101 @@
+// This file is part of the uutils util-linux package.
+//
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+
+use clap::{builder::PossibleValuesParser, crate_version, Arg, ArgAction, ArgMatches, Command};
+#[cfg(target_family = "unix")]
+use uucore::error::{set_exit_code, UIoError};
+use uucore::{error::UResult, format_usage, help_about, help_usage};
+
+const ABOUT: &str = help_about!("mesg.md");
+const USAGE: &str = help_usage!("mesg.md");
+
+#[cfg(target_family = "unix")]
+pub fn do_mesg(matches: &ArgMatches) -> UResult<()> {
+    use nix::sys::stat::{fchmod, fstat, Mode};
+    use std::{io, os::fd::AsRawFd};
+    use std::{io::IsTerminal, os::fd::AsFd};
+
+    for fd in &[
+        std::io::stdin().as_fd(),
+        std::io::stdout().as_fd(),
+        std::io::stderr().as_fd(),
+    ] {
+        if fd.is_terminal() {
+            let st = fstat(fd.as_raw_fd())?;
+            if let Some(enable) = matches.get_one::<String>("enable") {
+                // 'mesg y' on the GNU version seems to only modify the group write bit,
+                // but 'mesg n' modifies both group and others write bits.
+                let new_mode = if enable == "y" {
+                    st.st_mode | 0o020
+                } else {
+                    st.st_mode & !0o022
+                };
+                fchmod(fd.as_raw_fd(), Mode::from_bits_retain(new_mode))?;
+                if enable == "n" {
+                    set_exit_code(1);
+                }
+                if matches.get_flag("verbose") {
+                    println!(
+                        "write access to your terminal is {}",
+                        if enable == "y" { "allowed" } else { "denied" }
+                    );
+                }
+            } else if st.st_mode & 0o022 != 0 {
+                println!("is y");
+            } else {
+                set_exit_code(1);
+                println!("is n");
+            }
+            return Ok(());
+        }
+    }
+    Err(UIoError::new(
+        io::ErrorKind::Other,
+        "stdin/stdout/stderr is not a terminal",
+    ))
+}
+
+#[cfg(target_family = "unix")]
+#[uucore::main]
+pub fn uumain(args: impl uucore::Args) -> UResult<()> {
+    let matches = uu_app().try_get_matches_from(args)?;
+    if let Err(e) = do_mesg(&matches) {
+        set_exit_code(2);
+        uucore::show_error!("{}", e);
+    };
+    Ok(())
+}
+
+pub fn uu_app() -> Command {
+    Command::new(uucore::util_name())
+        .version(crate_version!())
+        .about(ABOUT)
+        .override_usage(format_usage(USAGE))
+        .infer_long_args(true)
+        .arg(
+            Arg::new("verbose")
+                .short('v')
+                .long("verbose")
+                .help("Explain what is being done")
+                .action(ArgAction::SetTrue),
+        )
+        .arg(
+            Arg::new("enable")
+                .help("Whether to allow or disallow messages")
+                .value_parser(PossibleValuesParser::new(["y", "n"]))
+                .action(ArgAction::Set),
+        )
+}
+
+#[cfg(not(target_family = "unix"))]
+#[uucore::main]
+pub fn uumain(args: impl uucore::Args) -> UResult<()> {
+    let _matches: ArgMatches = uu_app().try_get_matches_from(args)?;
+
+    Err(uucore::error::USimpleError::new(
+        1,
+        "`mesg` is available only on Unix platforms.",
+    ))
+}
diff --git a/tests/by-util/test_mesg.rs b/tests/by-util/test_mesg.rs
new file mode 100644
index 0000000..7715a8d
--- /dev/null
+++ b/tests/by-util/test_mesg.rs
@@ -0,0 +1,36 @@
+// This file is part of the uutils util-linux package.
+//
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+
+use crate::common::util::TestScenario;
+
+#[test]
+fn test_invalid_verb() {
+    new_ucmd!().arg("foo").fails().code_is(1);
+}
+
+#[test]
+#[cfg(target_family = "unix")]
+fn test_no_terminal() {
+    for args in &[vec![], vec!["y"], vec!["n"]] {
+        new_ucmd!()
+            .args(args)
+            .fails()
+            .code_is(2)
+            .stderr_contains("stdin/stdout/stderr is not a terminal");
+    }
+}
+
+#[cfg(not(target_family = "unix"))]
+mod non_unix {
+    use crate::common::util::TestScenario;
+
+    #[test]
+    fn test_fails_on_unsupported_platforms() {
+        new_ucmd!()
+            .fails()
+            .code_is(1)
+            .stderr_is("mesg: `mesg` is available only on Unix platforms.\n");
+    }
+}
diff --git a/tests/tests.rs b/tests/tests.rs
index ae15cde..8d72ee6 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -17,6 +17,10 @@ mod test_lsmem;
 #[path = "by-util/test_lslocks.rs"]
 mod test_lslocks;
 
+#[cfg(feature = "mesg")]
+#[path = "by-util/test_mesg.rs"]
+mod test_mesg;
+
 #[cfg(feature = "mountpoint")]
 #[path = "by-util/test_mountpoint.rs"]
 mod test_mountpoint;