diff --git a/src/roff/groff/groff.am b/src/roff/groff/groff.am
index 60872d02..f94a9beb 100644
--- a/src/roff/groff/groff.am
+++ b/src/roff/groff/groff.am
@@ -44,6 +44,7 @@ groff_TESTS = \
   src/roff/groff/tests/adjustment_works.sh \
   src/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh \
   src/roff/groff/tests/dot-cp_register_works.sh \
+  src/roff/groff/tests/msoquiet_works.sh \
   src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh \
   src/roff/groff/tests/recognize_end_of_sentence.sh \
   src/roff/groff/tests/regression_savannah_56555.sh \
@@ -52,6 +53,7 @@ groff_TESTS = \
   src/roff/groff/tests/regression_savannah_58337.sh \
   src/roff/groff/tests/regression_savannah_59202.sh \
   src/roff/groff/tests/smoke-test_html_device.sh \
+  src/roff/groff/tests/soquiet_works.sh \
   src/roff/groff/tests/string_case_xform_errors.sh \
   src/roff/groff/tests/string_case_xform_requests.sh \
   src/roff/groff/tests/string_case_xform_unicode_escape.sh \
diff --git a/src/roff/groff/tests/msoquiet_works.sh b/src/roff/groff/tests/msoquiet_works.sh
new file mode 100755
index 00000000..c232a1be
--- /dev/null
+++ b/src/roff/groff/tests/msoquiet_works.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+DOC='.msoquiet nonexistent'
+
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii 2>&1)
+
+echo "testing that .msoquiet of nonexistent file produces no warning" \
+  >&2
+test -z "$OUTPUT"
diff --git a/src/roff/groff/tests/soquiet_works.sh b/src/roff/groff/tests/soquiet_works.sh
new file mode 100755
index 00000000..8e8d71e7
--- /dev/null
+++ b/src/roff/groff/tests/soquiet_works.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+DOC='.soquiet nonexistent'
+
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii 2>&1)
+
+echo "testing that .soquiet of nonexistent file produces no error" >&2
+test -z "$OUTPUT"
diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp
index cb20e001..187a6a22 100644
--- a/src/roff/troff/input.cpp
+++ b/src/roff/troff/input.cpp
@@ -5956,9 +5956,7 @@ void while_continue_request()
   }
 }
 
-// .so
-
-void source()
+void do_source(bool quietly)
 {
   symbol nm = get_long_name(1);
   if (nm.is_null())
@@ -5971,11 +5969,34 @@ void source()
     if (fp)
       input_stack::push(new file_iterator(fp, nm.contents()));
     else
-      error("can't open '%1': %2", nm.contents(), strerror(errno));
+      // Suppress diagnostic only if we're operating quietly and it's an
+      // expected problem.
+      while (1) {
+	if (quietly)
+	  if ((EACCES == errno) || (ENOENT == errno))
+	    break;
+	error("can't open '%1': %2", nm.contents(), strerror(errno));
+	break;
+      }
     tok.next();
   }
 }
 
+// .so
+
+void source()
+{
+  do_source(0); // 0: not quietly
+}
+
+// .soquiet: like .so, but silently ignore files that can't be opened
+// due to their nonexistence or lack of permissions
+
+void source_quietly()
+{
+  do_source(1); // 1: quietly
+}
+
 // like .so but use popen()
 
 void pipe_source()
@@ -7771,7 +7792,7 @@ static void process_startup_file(const char *filename)
   mac_path = orig_mac_path;
 }
 
-void macro_source()
+void do_macro_source(bool quietly)
 {
   symbol nm = get_long_name(1);
   if (nm.is_null())
@@ -7810,11 +7831,35 @@ void macro_source()
       free(path);
     }
     else
-      warning(WARN_FILE, "can't find macro file '%1'", nm.contents());
+      // Suppress diagnostic only if we're operating quietly and it's an
+      // expected problem.
+      while (1) {
+	if (quietly)
+	  if ((EACCES == errno) || (ENOENT == errno))
+	    break;
+	warning(WARN_FILE, "can't open macro file '%1': %2",
+		nm.contents(), strerror(errno));
+	break;
+      }
     tok.next();
   }
 }
 
+// .mso
+
+void macro_source()
+{
+  do_macro_source(0); // 0: not quietly (if WARN_FILE enabled)
+}
+
+// .msoquiet: like .mso, but silently ignore files that can't be opened
+// due to their nonexistence or lack of permissions
+
+void macro_source_quietly()
+{
+  do_macro_source(1); // 1: quietly
+}
+
 static void process_input_file(const char *name)
 {
   FILE *fp;
@@ -8275,6 +8320,7 @@ void init_input_requests()
   init_request("lf", line_file);
   init_request("lsm", leading_spaces_macro);
   init_request("mso", macro_source);
+  init_request("msoquiet", macro_source_quietly);
   init_request("nop", nop_request);
   init_request("nroff", nroff_request);
   init_request("nx", next_file);
@@ -8296,6 +8342,7 @@ void init_input_requests()
   init_request("schar", define_special_character);
   init_request("shift", shift);
   init_request("so", source);
+  init_request("soquiet", source_quietly);
   init_request("spreadwarn", spreadwarn_request);
   init_request("stringdown", stringdown_request);
   init_request("stringup", stringup_request);
