pw_toolchain: Implement Newlib OS interface functions

Starting in arm-none-eabi-gcc 11.3, the compiler outputs warnings if
Newlib OS interface functions are called but not implemented. A default
that returns an error is used in that case. The Newlib OS interface is
documented at https://sourceware.org/newlib/libc.html#Stubs.

Most of the Newlib OS interface functions should never be called in
Pigweed. Implement these functions but have them invoke an undefined
function to force a linker error if they are used.

In addition to implementing the OS interface, wrap a few unnecessary
Newlib functions that would otherwise cause several OS interface
functions to be linked in. Abort if these functions are called.

Change-Id: Iee0a73598b6daed175ca8328053386460b85b74a
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/112311
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Armando Montanez <amontanez@google.com>
diff --git a/pw_toolchain/CMakeLists.txt b/pw_toolchain/CMakeLists.txt
index d587634..300c4a3 100644
--- a/pw_toolchain/CMakeLists.txt
+++ b/pw_toolchain/CMakeLists.txt
@@ -14,6 +14,8 @@
 
 include("$ENV{PW_ROOT}/pw_build/pigweed.cmake")
 
+add_subdirectory(arm_gcc EXCLUDE_FROM_ALL)
+
 pw_add_library(pw_toolchain.wrap_abort STATIC
   SOURCES
     wrap_abort.cc
diff --git a/pw_toolchain/arm_gcc/BUILD.bazel b/pw_toolchain/arm_gcc/BUILD.bazel
new file mode 100644
index 0000000..79cfb7f
--- /dev/null
+++ b/pw_toolchain/arm_gcc/BUILD.bazel
@@ -0,0 +1,34 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+pw_cc_library(
+    name = "newlib_os_interface_stubs",
+    srcs = ["newlib_os_interface_stubs.cc"],
+    linkopts = [
+        "-Wl,--wrap=__sread",
+        "-Wl,--wrap=__swrite",
+        "-Wl,--wrap=__sseek",
+        "-Wl,--wrap=__sclose",
+    ],
+    deps = ["//pw_assert"],
+)
diff --git a/pw_toolchain/arm_gcc/BUILD.gn b/pw_toolchain/arm_gcc/BUILD.gn
index 78bb93d..fad0fcc 100644
--- a/pw_toolchain/arm_gcc/BUILD.gn
+++ b/pw_toolchain/arm_gcc/BUILD.gn
@@ -15,6 +15,8 @@
 import("//build_overrides/pigweed.gni")
 import("//build_overrides/pigweed_environment.gni")
 
+import("$dir_pw_build/target_types.gni")
+
 # Disable obnoxious ABI warning.
 #
 # GCC 7.1 adds an over-zealous ABI warning with little useful information
@@ -118,3 +120,22 @@
   defines = [ "PW_ARMV7M_ENABLE_FPU=1" ]
   ldflags = cflags
 }
+
+config("wrap_newlib_stdio_functions") {
+  ldflags = [
+    "-Wl,--wrap=__sread",
+    "-Wl,--wrap=__swrite",
+    "-Wl,--wrap=__sseek",
+    "-Wl,--wrap=__sclose",
+  ]
+  visibility = [ ":*" ]
+}
+
+pw_source_set("newlib_os_interface_stubs") {
+  all_dependent_configs = [ ":wrap_newlib_stdio_functions" ]
+  sources = [ "newlib_os_interface_stubs.cc" ]
+  deps = [
+    "$dir_pw_toolchain:wrap_abort",
+    dir_pw_assert,
+  ]
+}
diff --git a/pw_toolchain/arm_gcc/CMakeLists.txt b/pw_toolchain/arm_gcc/CMakeLists.txt
new file mode 100644
index 0000000..c353a70
--- /dev/null
+++ b/pw_toolchain/arm_gcc/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+include("$ENV{PW_ROOT}/pw_build/pigweed.cmake")
+
+pw_add_library(pw_toolchain.arm_gcc.newlib_os_interface_stubs STATIC
+  SOURCES
+    newlib_os_interface_stubs.cc
+  PUBLIC_DEPS
+    pw_assert
+  PUBLIC_LINK_OPTIONS
+    "-Wl,--wrap=__sread"
+    "-Wl,--wrap=__swrite"
+    "-Wl,--wrap=__sseek"
+    "-Wl,--wrap=__sclose"
+)
diff --git a/pw_toolchain/arm_gcc/newlib_os_interface_stubs.cc b/pw_toolchain/arm_gcc/newlib_os_interface_stubs.cc
new file mode 100644
index 0000000..03184e4
--- /dev/null
+++ b/pw_toolchain/arm_gcc/newlib_os_interface_stubs.cc
@@ -0,0 +1,103 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <sys/stat.h>   // POSIX header provided by Newlib; needed for stat.
+#include <sys/times.h>  // POSIX header provided by Newlib; needed for times.
+
+#include <cstdio>  // fpos_t
+
+#include "pw_assert/check.h"
+
+namespace pw::toolchain {
+namespace {
+
+[[noreturn]] void AbortIfUnsupportedNewlibFunctionIsCalled() {
+  PW_CRASH(
+      "Attempted to invoke an unsupported Newlib function! The stdout and "
+      "stderr FILE objects are not supported.");
+}
+
+}  // namespace
+
+// Wrap the stdio read, write, seek, and close Newlib functions defined in
+// libc/stdio/stdio.c. These should never be called, so abort if they are.
+//
+// These functions are unconditionally linked, even if they're never called,
+// because they are assigned as members of the stdout/stderr FILE struct. The
+// Newlib implementations invoke some of the unsupported OS interface functions.
+#define PW_WRAP_NEWLIB_FILE_FUNCTION(function, ...) \
+  extern "C" int __wrap_##function(__VA_ARGS__) {   \
+    AbortIfUnsupportedNewlibFunctionIsCalled();     \
+  }
+
+PW_WRAP_NEWLIB_FILE_FUNCTION(__sread, void*, char*, int)
+PW_WRAP_NEWLIB_FILE_FUNCTION(__swrite, void*, char*, int)
+PW_WRAP_NEWLIB_FILE_FUNCTION(__sseek, void*, fpos_t, int)
+PW_WRAP_NEWLIB_FILE_FUNCTION(__sclose, void*)
+
+#undef PW_WRAP_NEWLIB_FILE_FUNCTION
+
+// Newlib defines a set of OS interface functions. Most of these should never be
+// called, since they're used by libc functions not supported in Pigweed(e.g.
+// fopen or printf). If they're linked into a binary, that indicates that an
+// unsupported function was called.
+//
+// Newlib provides default, nop implementations of these functions. Starting
+// with arm-none-eabi-gcc 11.3, a warning is issued when any of these defaults
+// are used.
+//
+// Provide implementations for most of the Newlib OS interface functions, which
+// are documented at https://sourceware.org/newlib/libc.html#Stubs. The default
+// implementation calls the following function, which is never defined,
+// resulting in a linker error.
+[[noreturn]] void AttempedToInvokeUnsupportedNewlibOsInterfaceFunction();
+
+#define PW_DISABLE_NEWLIB_FUNCTION(function, ...)           \
+  extern "C" int _##function(__VA_ARGS__) {                 \
+    AttempedToInvokeUnsupportedNewlibOsInterfaceFunction(); \
+  }
+
+PW_DISABLE_NEWLIB_FUNCTION(_exit, void)
+PW_DISABLE_NEWLIB_FUNCTION(close, int)
+PW_DISABLE_NEWLIB_FUNCTION(execve, char*, char**, char**)
+PW_DISABLE_NEWLIB_FUNCTION(fork, void)
+
+// Provide the minimal fstat implementation recommended by the Newlib
+// documentation since fstat is called indirectly by snprintf.
+extern "C" int _fstat(int, struct stat* st) {
+  st->st_mode = S_IFCHR;
+  return 0;
+}
+
+PW_DISABLE_NEWLIB_FUNCTION(getpid, void)
+
+// Provide the minimal isatty implementation recommended by the Newlib
+// documentation since isatty is called indirectly by snprintf.
+extern "C" int _isatty(int) { return 1; }
+
+PW_DISABLE_NEWLIB_FUNCTION(kill, int, int)
+PW_DISABLE_NEWLIB_FUNCTION(link, char*, char*)
+PW_DISABLE_NEWLIB_FUNCTION(lseek, int, int, int)
+PW_DISABLE_NEWLIB_FUNCTION(open, const char*, int, int)
+PW_DISABLE_NEWLIB_FUNCTION(read, int, char*, int)
+PW_DISABLE_NEWLIB_FUNCTION(sbrk, int)
+PW_DISABLE_NEWLIB_FUNCTION(stat, char*, struct stat*)
+PW_DISABLE_NEWLIB_FUNCTION(times, struct tms*)
+PW_DISABLE_NEWLIB_FUNCTION(unlink, char*)
+PW_DISABLE_NEWLIB_FUNCTION(wait, int*)
+PW_DISABLE_NEWLIB_FUNCTION(write, int, char*, int)
+
+#undef PW_DISABLE_NEWLIB_FUNCTION
+
+}  // namespace pw::toolchain
diff --git a/pw_toolchain/docs.rst b/pw_toolchain/docs.rst
index 2b58ca7..77fd6c4 100644
--- a/pw_toolchain/docs.rst
+++ b/pw_toolchain/docs.rst
@@ -155,3 +155,17 @@
 ``abort`` in builds where the default behavior is undesirable. It uses the
 ``-Wl,--wrap=abort`` linker option to redirect to ``abort`` calls to
 ``PW_CRASH`` instead.
+
+Newlib OS interface
+===================
+`Newlib <https://sourceware.org/newlib/>`_, the C Standard Library
+implementation provided with ``arm-none-eabi-gcc``, defines a set of `OS
+interface functions <https://sourceware.org/newlib/libc.html#Stubs>`_ that
+should be implemented. A default is provided if these functions are not
+implemented, but using the default results in a compiler warning.
+
+Most of the OS interface functions should never be called in embedded builds.
+The ``pw_toolchain/arg_gcc:newlib_os_interface_stubs`` library implements these
+functions and forces a linker error if they are used. It also wraps some
+functions related to use of ``stdout`` and ``stderr`` that abort if they are
+called.
diff --git a/targets/stm32f429i_disc1/target_toolchains.gni b/targets/stm32f429i_disc1/target_toolchains.gni
index 183f2a9..5022588 100644
--- a/targets/stm32f429i_disc1/target_toolchains.gni
+++ b/targets/stm32f429i_disc1/target_toolchains.gni
@@ -87,6 +87,7 @@
     "$dir_pw_cpu_exception:entry_impl",
     "$dir_pw_log:impl",
     "$dir_pw_toolchain:wrap_abort",
+    "$dir_pw_toolchain/arm_gcc:newlib_os_interface_stubs",
   ]
 
   current_cpu = "arm"