diff --git a/libc/src/__support/CPP/string.h b/libc/src/__support/CPP/string.h
--- a/libc/src/__support/CPP/string.h
+++ b/libc/src/__support/CPP/string.h
@@ -113,6 +113,8 @@
     return string_view(buffer_, size_);
   }
 
+  LIBC_INLINE explicit operator char *() const { return buffer_; }
+
   LIBC_INLINE void reserve(size_t new_capacity) {
     ++new_capacity; // Accounting for the terminating '\0'
     if (new_capacity <= capacity_)
diff --git a/libc/src/stdio/fprintf.cpp b/libc/src/stdio/fprintf.cpp
--- a/libc/src/stdio/fprintf.cpp
+++ b/libc/src/stdio/fprintf.cpp
@@ -8,7 +8,6 @@
 
 #include "src/stdio/fprintf.h"
 
-#include "src/__support/File/file.h"
 #include "src/__support/arg_list.h"
 #include "src/stdio/printf_core/vfprintf_internal.h"
 
@@ -18,6 +17,7 @@
 namespace __llvm_libc {
 
 #ifndef LIBC_COPT_PRINTF_USE_SYSTEM_FILE
+#include "src/__support/File/file.h"
 using FileT = __llvm_libc::File;
 #else  // defined(LIBC_COPT_PRINTF_USE_SYSTEM_FILE)
 using FileT = ::FILE;
diff --git a/libc/test/UnitTest/BazelFilePath.cpp b/libc/test/UnitTest/BazelFilePath.cpp
new file mode 100644
--- /dev/null
+++ b/libc/test/UnitTest/BazelFilePath.cpp
@@ -0,0 +1,27 @@
+//===-- Implementation of the file path generator for bazel ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "LibcTest.h"
+
+#include <stdlib.h>
+
+#include "src/__support/CPP/string.h"
+#include "utils/testutils/OwnedString.h"
+
+namespace __llvm_libc::testing {
+
+// This is the path to the folder bazel wants the test outputs written to.
+static const char *const UNDECLARED_OUTPUTS_PATH =
+    getenv("TEST_UNDECLARED_OUTPUTS_DIR");
+
+testutils::OwnedString<cpp::string>
+libc_make_test_file_path_func(const char *file_name) {
+  return cpp::string(UNDECLARED_OUTPUTS_PATH) + file_name;
+}
+
+} // namespace __llvm_libc::testing
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -14,6 +14,7 @@
   LibcUnitTest
   Test.h
   LibcTest.cpp
+  CmakeFilePath.cpp
   LibcTest.h
 )
 target_include_directories(LibcUnitTest PUBLIC ${LIBC_SOURCE_DIR})
diff --git a/libc/test/UnitTest/CmakeFilePath.cpp b/libc/test/UnitTest/CmakeFilePath.cpp
new file mode 100644
--- /dev/null
+++ b/libc/test/UnitTest/CmakeFilePath.cpp
@@ -0,0 +1,23 @@
+//===-- Implementation of the file path generator for cmake ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "LibcTest.h"
+
+#include "src/__support/CPP/string.h"
+#include "utils/testutils/OwnedString.h"
+
+namespace __llvm_libc::testing {
+
+constexpr char PREFIX[] = "testdata/";
+
+testutils::OwnedString<cpp::string>
+libc_make_test_file_path_func(const char *file_name) {
+  return cpp::string(PREFIX) + file_name;
+}
+
+} // namespace __llvm_libc::testing
diff --git a/libc/test/UnitTest/LibcTest.h b/libc/test/UnitTest/LibcTest.h
--- a/libc/test/UnitTest/LibcTest.h
+++ b/libc/test/UnitTest/LibcTest.h
@@ -9,6 +9,20 @@
 #ifndef LLVM_LIBC_UTILS_UNITTEST_LIBCTEST_H
 #define LLVM_LIBC_UTILS_UNITTEST_LIBCTEST_H
 
+// This is defined as a simple macro in test.h so that it exists for platforms
+// that don't use our test infrastructure. It's defined as a proper function
+// below.
+#ifdef libc_make_test_file_path
+#undef libc_make_test_file_path
+#endif // libc_make_test_file_path
+
+// This macro takes a string literal and returns a value implicitly castable to
+// a const char*. That char* is the path to a file with the provided name in a
+// directory where the test is allowed to write. By default it uses the prefix
+// "testdata/", but implementations are allowed to redefine it as necessary.
+#define libc_make_test_file_path(file_name)                                    \
+  (__llvm_libc::testing::libc_make_test_file_path_func("" file_name))
+
 // This file can only include headers from src/__support/CPP/ or
 // utils/testutils. No other headers should be included.
 
@@ -18,6 +32,7 @@
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/CPP/type_traits.h"
 #include "utils/testutils/ExecuteFunction.h"
+#include "utils/testutils/OwnedString.h"
 #include "utils/testutils/StreamWrapper.h"
 
 namespace __llvm_libc {
@@ -247,6 +262,9 @@
 // Make TypeList visible in __llvm_libc::testing.
 template <typename... Types> using TypeList = internal::TypeList<Types...>;
 
+testutils::OwnedString<cpp::string>
+libc_make_test_file_path_func(const char *file_name);
+
 } // namespace testing
 } // namespace __llvm_libc
 
diff --git a/libc/test/UnitTest/Test.h b/libc/test/UnitTest/Test.h
--- a/libc/test/UnitTest/Test.h
+++ b/libc/test/UnitTest/Test.h
@@ -9,6 +9,12 @@
 #ifndef LLVM_LIBC_UTILS_UNITTEST_TEST_H
 #define LLVM_LIBC_UTILS_UNITTEST_TEST_H
 
+// This macro takes a string literal and returns a value implicitly castable to
+// a const char*. That char* is the path to a file with the provided name in a
+// directory where the test is allowed to write. By default it uses the prefix
+// "testdata/", but implementations are allowed to redefine it as necessary.
+#define libc_make_test_file_path(file_name) ("testdata/" file_name)
+
 #ifdef LIBC_COPT_TEST_USE_FUCHSIA
 #include "FuchsiaTest.h"
 #elif defined(LIBC_COPT_TEST_USE_PIGWEED)
diff --git a/libc/test/src/stdio/fprintf_test.cpp b/libc/test/src/stdio/fprintf_test.cpp
--- a/libc/test/src/stdio/fprintf_test.cpp
+++ b/libc/test/src/stdio/fprintf_test.cpp
@@ -34,8 +34,9 @@
 } // namespace printf_test
 
 TEST(LlvmLibcFPrintfTest, WriteToFile) {
-  constexpr char FILENAME[] = "testdata/fprintf_output.test";
-  ::FILE *file = printf_test::fopen(FILENAME, "w");
+  auto FILE_PATH = libc_make_test_file_path("fprintf_output.test");
+
+  ::FILE *file = printf_test::fopen(FILE_PATH, "w");
   ASSERT_FALSE(file == nullptr);
 
   int written;
@@ -55,7 +56,7 @@
 
   ASSERT_EQ(0, printf_test::fclose(file));
 
-  file = printf_test::fopen(FILENAME, "r");
+  file = printf_test::fopen(FILE_PATH, "r");
   ASSERT_FALSE(file == nullptr);
 
   char data[50];
diff --git a/libc/utils/testutils/CMakeLists.txt b/libc/utils/testutils/CMakeLists.txt
--- a/libc/utils/testutils/CMakeLists.txt
+++ b/libc/utils/testutils/CMakeLists.txt
@@ -16,4 +16,5 @@
   Timer.h
   Timer.cpp
   RoundingModeUtils.cpp
+  OwnedString.h
 )
diff --git a/libc/utils/testutils/OwnedString.h b/libc/utils/testutils/OwnedString.h
new file mode 100644
--- /dev/null
+++ b/libc/utils/testutils/OwnedString.h
@@ -0,0 +1,28 @@
+//===-- Implementation of a struct to hold a string in menory -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_UTILS_TESTUTILS_OWNEDSTRING_H
+#define LLVM_LIBC_UTILS_TESTUTILS_OWNEDSTRING_H
+
+namespace __llvm_libc::testutils {
+
+template <typename Str_T> class OwnedString {
+  Str_T str;
+  const char *cstr;
+
+public:
+  inline OwnedString(Str_T in_str) {
+    str = in_str;
+    cstr = static_cast<const char *>(str);
+  }
+
+  inline operator const char *() const { return cstr; }
+};
+} // namespace __llvm_libc::testutils
+
+#endif // LLVM_LIBC_UTILS_TESTUTILS_OWNEDSTRING_H
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -5,6 +5,7 @@
 # LLVM libc project.
 load(
     ":libc_build_rules.bzl",
+    "PRINTF_COPTS",
     "libc_function",
     "libc_math_function",
     "libc_support_library",
@@ -152,6 +153,14 @@
     ],
 )
 
+libc_support_library(
+    name = "__support_cpp_expected",
+    hdrs = ["src/__support/CPP/expected.h"],
+    deps = [
+        ":libc_root",
+    ],
+)
+
 libc_support_library(
     name = "__support_cpp_functional",
     hdrs = ["src/__support/CPP/functional.h"],
@@ -164,6 +173,16 @@
     deps = [":libc_root"],
 )
 
+libc_support_library(
+    name = "__support_cpp_new",
+    srcs = ["src/__support/CPP/new.cpp"],
+    hdrs = ["src/__support/CPP/new.h"],
+    deps = [
+        ":__support_common",
+        ":libc_root",
+    ],
+)
+
 libc_support_library(
     name = "__support_cpp_optional",
     hdrs = ["src/__support/CPP/optional.h"],
@@ -237,6 +256,16 @@
     ],
 )
 
+libc_support_library(
+    name = "__support_error_or",
+    hdrs = ["src/__support/error_or.h"],
+    deps = [
+        ":__support_common",
+        ":__support_cpp_expected",
+        ":libc_root",
+    ],
+)
+
 libc_support_library(
     name = "__support_float_to_string",
     hdrs = [
@@ -391,6 +420,20 @@
     ],
 )
 
+libc_support_library(
+    name = "__support_file_file",
+    srcs = ["src/__support/File/file.cpp"],
+    hdrs = ["src/__support/File/file.h"],
+    deps = [
+        ":__support_cpp_new",
+        ":__support_cpp_span",
+        ":__support_error_or",
+        ":__support_threads_mutex",
+        ":errno",
+        ":libc_root",
+    ],
+)
+
 libc_support_library(
     name = "__support_named_pair",
     hdrs = ["src/__support/named_pair.h"],
@@ -694,6 +737,23 @@
     ],
 )
 
+libc_support_library(
+    name = "__support_threads_mutex",
+    hdrs = [
+        "src/__support/threads/mutex.h",
+        "src/__support/threads/mutex_common.h",
+    ],
+    textual_hdrs = [
+        "src/__support/threads/linux/mutex.h",
+        "src/__support/threads/linux/futex_word.h",
+    ],
+    deps = [
+        ":__support_cpp_atomic",
+        ":__support_osutil_syscall",
+        ":libc_root",
+    ],
+)
+
 ############################### errno targets ################################
 
 libc_function(
@@ -2071,6 +2131,7 @@
 libc_support_library(
     name = "printf_core_structs",
     hdrs = ["src/stdio/printf_core/core_structs.h"],
+    copts = PRINTF_COPTS,
     deps = [
         ":__support_cpp_string_view",
         ":__support_fputil_fp_bits",
@@ -2081,6 +2142,7 @@
 libc_support_library(
     name = "printf_config",
     hdrs = ["src/stdio/printf_core/printf_config.h"],
+    copts = PRINTF_COPTS,
     deps = [
         ":libc_root",
     ],
@@ -2090,6 +2152,7 @@
     name = "printf_parser",
     srcs = ["src/stdio/printf_core/parser.cpp"],
     hdrs = ["src/stdio/printf_core/parser.h"],
+    copts = PRINTF_COPTS,
     deps = [
         ":__support_arg_list",
         ":__support_common",
@@ -2111,7 +2174,7 @@
     name = "printf_mock_parser",
     srcs = ["src/stdio/printf_core/parser.cpp"],
     hdrs = ["src/stdio/printf_core/parser.h"],
-    copts = ["-DLIBC_COPT_MOCK_ARG_LIST"],
+    copts = PRINTF_COPTS + ["-DLIBC_COPT_MOCK_ARG_LIST"],
     deps = [
         ":__support_arg_list",
         ":__support_common",
@@ -2132,6 +2195,7 @@
     name = "printf_string_writer",
     srcs = ["src/stdio/printf_core/string_writer.cpp"],
     hdrs = ["src/stdio/printf_core/string_writer.h"],
+    copts = PRINTF_COPTS,
     deps = [
         ":__support_cpp_string_view",
         ":libc_root",
@@ -2140,10 +2204,23 @@
     ],
 )
 
+libc_support_library(
+    name = "printf_file_writer",
+    hdrs = ["src/stdio/printf_core/file_writer.h"],
+    copts = PRINTF_COPTS,
+    deps = [
+        ":__support_cpp_string_view",
+        ":__support_file_file",
+        ":libc_root",
+        ":printf_core_structs",
+    ],
+)
+
 libc_support_library(
     name = "printf_writer",
     srcs = ["src/stdio/printf_core/writer.cpp"],
     hdrs = ["src/stdio/printf_core/writer.h"],
+    copts = PRINTF_COPTS,
     deps = [
         ":__support_cpp_string_view",
         ":libc_root",
@@ -2166,6 +2243,7 @@
         "src/stdio/printf_core/string_converter.h",
         "src/stdio/printf_core/write_int_converter.h",
     ],
+    copts = PRINTF_COPTS,
     deps = [
         ":__support_common",
         ":__support_cpp_limits",
@@ -2188,6 +2266,7 @@
     name = "printf_main",
     srcs = ["src/stdio/printf_core/printf_main.cpp"],
     hdrs = ["src/stdio/printf_core/printf_main.h"],
+    copts = PRINTF_COPTS,
     deps = [
         ":__support_arg_list",
         ":libc_root",
@@ -2202,6 +2281,7 @@
     name = "sprintf",
     srcs = ["src/stdio/sprintf.cpp"],
     hdrs = ["src/stdio/sprintf.h"],
+    copts = PRINTF_COPTS,
     deps = [
         ":__support_arg_list",
         ":errno",
@@ -2215,6 +2295,7 @@
     name = "snprintf",
     srcs = ["src/stdio/snprintf.cpp"],
     hdrs = ["src/stdio/snprintf.h"],
+    copts = PRINTF_COPTS,
     deps = [
         ":__support_arg_list",
         ":errno",
@@ -2223,3 +2304,41 @@
         ":printf_writer",
     ],
 )
+
+libc_support_library(
+    name = "vfprintf_internal",
+    hdrs = ["src/stdio/printf_core/vfprintf_internal.h"],
+    copts = PRINTF_COPTS,
+    deps = [
+        ":__support_arg_list",
+        ":__support_file_file",
+        ":__support_macros_attributes",
+        ":printf_file_writer",
+        ":printf_main",
+        ":printf_writer",
+    ],
+)
+
+libc_function(
+    name = "printf",
+    srcs = ["src/stdio/printf.cpp"],
+    hdrs = ["src/stdio/printf.h"],
+    copts = PRINTF_COPTS,
+    deps = [
+        ":__support_arg_list",
+        ":errno",
+        ":vfprintf_internal",
+    ],
+)
+
+libc_function(
+    name = "fprintf",
+    srcs = ["src/stdio/fprintf.cpp"],
+    hdrs = ["src/stdio/fprintf.h"],
+    copts = PRINTF_COPTS,
+    deps = [
+        ":__support_arg_list",
+        ":errno",
+        ":vfprintf_internal",
+    ],
+)
diff --git a/utils/bazel/llvm-project-overlay/libc/libc_build_rules.bzl b/utils/bazel/llvm-project-overlay/libc/libc_build_rules.bzl
--- a/utils/bazel/llvm-project-overlay/libc/libc_build_rules.bzl
+++ b/utils/bazel/llvm-project-overlay/libc/libc_build_rules.bzl
@@ -10,6 +10,12 @@
 LIBC_ROOT_TARGET = ":libc_root"
 INTERNAL_SUFFIX = ".__internal__"
 
+PRINTF_COPTS = [
+    "-DLIBC_COPT_PRINTF_USE_SYSTEM_FILE",
+    "-DLIBC_COPT_PRINTF_DISABLE_INDEX_MODE",
+    "-DLIBC_COPT_PRINTF_DISABLE_WRITE_INT",
+]
+
 def _libc_library(name, copts = None, **kwargs):
     """Internal macro to serve as a base for all other libc library rules.
 
@@ -23,7 +29,7 @@
     # We want all libc sources to be compiled with "hidden" visibility.
     # The public symbols will be given "default" visibility explicitly.
     # See src/__support/common.h for more information.
-    copts.append("-fvisibility=hidden")
+    copts = copts + ["-fvisibility=hidden"]
     native.cc_library(
         name = name,
         copts = copts,
@@ -65,11 +71,11 @@
       **kwargs: Other attributes relevant for a cc_library. For example, deps.
     """
     deps = deps or []
-    deps.append(LIBC_ROOT_TARGET)
+    # We use the explicit equals pattern here because append and += mutate the
+    # original list, where this creates a new list and stores it in deps.
+    deps = deps + [LIBC_ROOT_TARGET]
     copts = copts or []
-    copts.append("-O3")
-    copts.append("-fno-builtin")
-    copts.append("-fno-lax-vector-conversions")
+    copts = copts + ["-O3", "-fno-builtin", "-fno-lax-vector-conversions"]
 
     # We compile the code twice, the first target is suffixed with ".__internal__" and contains the
     # C++ functions in the "__llvm_libc" namespace. This allows us to test the function in the
@@ -87,9 +93,9 @@
 
     func_attrs = ["__attribute__((visibility(\"default\")))"]
     if weak:
-        func_attrs.append("__attribute__((weak))")
+        func_attrs = func_attrs + ["__attribute__((weak))"]
     local_defines = local_defines or ["LIBC_COPT_PUBLIC_PACKAGING"]
-    local_defines.append("LLVM_LIBC_FUNCTION_ATTR='%s'" % " ".join(func_attrs))
+    local_defines = local_defines + ["LLVM_LIBC_FUNCTION_ATTR='%s'" % " ".join(func_attrs)]
     _libc_library(
         name = name,
         srcs = srcs,
diff --git a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
--- a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
@@ -23,6 +23,7 @@
 cc_library(
     name = "LibcUnitTest",
     srcs = [
+        "BazelFilePath.cpp",
         "LibcTest.cpp",
         "LibcTestMain.cpp",
     ],
diff --git a/utils/bazel/llvm-project-overlay/libc/test/libc_test_rules.bzl b/utils/bazel/llvm-project-overlay/libc/test/libc_test_rules.bzl
--- a/utils/bazel/llvm-project-overlay/libc/test/libc_test_rules.bzl
+++ b/utils/bazel/llvm-project-overlay/libc/test/libc_test_rules.bzl
@@ -14,7 +14,7 @@
 
 load("//libc:libc_build_rules.bzl", "INTERNAL_SUFFIX")
 
-def libc_test(name, srcs, libc_function_deps, deps = [], **kwargs):
+def libc_test(name, srcs, libc_function_deps, deps = [], output_file = "", **kwargs):
     """Add target for a libc test.
 
     Args:
diff --git a/utils/bazel/llvm-project-overlay/libc/test/src/stdio/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/src/stdio/BUILD.bazel
--- a/utils/bazel/llvm-project-overlay/libc/test/src/stdio/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/test/src/stdio/BUILD.bazel
@@ -5,6 +5,7 @@
 # Tests for LLVM libc stdio.h functions.
 
 load("//libc/test:libc_test_rules.bzl", "libc_test")
+load("//libc:libc_build_rules.bzl", "PRINTF_COPTS")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -24,6 +25,7 @@
       "//libc/utils/testutils:libc_test_utils",
       "//libc/test/UnitTest:printf_matcher",
     ],
+    copts = PRINTF_COPTS,
 )
 
 libc_test(
@@ -38,6 +40,7 @@
       "//libc:__support_cpp_string_view",
       "//libc:__support_arg_list",
     ],
+    copts = PRINTF_COPTS,
 )
 
 libc_test(
@@ -53,6 +56,7 @@
       "//libc:__support_cpp_string_view",
       "//libc:__support_arg_list",
     ],
+    copts = PRINTF_COPTS,
 )
 
 libc_test(
@@ -66,6 +70,7 @@
       "//libc:__support_fputil_platform_defs",
       "//libc/utils/testutils:libc_test_utils",
     ],
+    copts = PRINTF_COPTS,
 )
 
 libc_test(
@@ -74,4 +79,23 @@
     libc_function_deps = [
         "//libc:snprintf",
     ],
+    copts = PRINTF_COPTS,
+)
+
+libc_test(
+    name = "printf_test",
+    srcs = ["printf_test.cpp"],
+    libc_function_deps = [
+        "//libc:printf",
+    ],
+    copts = PRINTF_COPTS,
+)
+
+libc_test(
+    name = "fprintf_test",
+    srcs = ["fprintf_test.cpp"],
+    libc_function_deps = [
+        "//libc:fprintf",
+    ],
+    copts = PRINTF_COPTS,
 )
diff --git a/utils/bazel/llvm-project-overlay/libc/utils/testutils/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/utils/testutils/BUILD.bazel
--- a/utils/bazel/llvm-project-overlay/libc/utils/testutils/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/utils/testutils/BUILD.bazel
@@ -17,6 +17,7 @@
     hdrs = [
         "ExecuteFunction.h",
         "FDReader.h",
+        "OwnedString.h",
         "RoundingModeUtils.h",
         "StreamWrapper.h",
     ],