From c286ad2868a3ab59b234a91d44f09dc04ffbba0f Mon Sep 17 00:00:00 2001
From: Anhad Singh <andypython@protonmail.com>
Date: Thu, 5 Dec 2024 23:03:52 +1100
Subject: [PATCH] fix(ld.so): do not depend on TLS at all

In the next big refactor (next PR), all of the platform functionality
used by both relibc and ld.so will be moved into a `platform`/`sysdeps`
crate and then ld.so would be moved out of relibc and not link with it.

I think doing it in a seperate PR would make it more managable, as when
I did half of it, the diff was pretty huge and that way it would be
easier to review too :)

Signed-off-by: Anhad Singh <andypython@protonmail.com>
---
 Makefile                                    |  4 +-
 ld_so/ld_script/aarch64-unknown-redox.ld    | 21 ++++++++---
 ld_so/ld_script/i686-unknown-redox.ld       | 21 ++++++++---
 ld_so/ld_script/riscv64gc-unknown-redox.ld  | 21 ++++++++---
 ld_so/ld_script/x86_64-unknown-linux-gnu.ld | 23 ++++++++----
 ld_so/ld_script/x86_64-unknown-redox.ld     | 23 ++++++++----
 ld_so/src/lib.rs                            |  4 +-
 src/header/netdb/mod.rs                     |  2 +-
 src/header/stdio/mod.rs                     |  2 +-
 src/ld_so/dso.rs                            | 19 +++-------
 src/ld_so/linker.rs                         | 41 +++++++++++++++------
 src/ld_so/start.rs                          | 25 +------------
 src/ld_so/tcb.rs                            | 11 ++----
 src/lib.rs                                  |  6 +--
 src/macros.rs                               |  4 +-
 src/platform/mod.rs                         | 18 ++++++---
 16 files changed, 143 insertions(+), 102 deletions(-)

diff --git a/Makefile b/Makefile
index 2a734b50c..ca3737a68 100644
--- a/Makefile
+++ b/Makefile
@@ -200,7 +200,7 @@ $(BUILD)/debug/ld_so.o: $(SRC)
 	touch $@
 
 $(BUILD)/debug/ld_so: $(BUILD)/debug/ld_so.o $(BUILD)/debug/crti.o $(BUILD)/debug/libc.a $(BUILD)/debug/crtn.o
-	$(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections --gc-keep-exported $^ -o $@
+	$(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections $^ -o $@
 
 # Release targets
 
@@ -240,7 +240,7 @@ $(BUILD)/release/ld_so.o: $(SRC)
 	touch $@
 
 $(BUILD)/release/ld_so: $(BUILD)/release/ld_so.o $(BUILD)/release/crti.o $(BUILD)/release/libc.a $(BUILD)/release/crtn.o
-	$(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections --gc-keep-exported $^ -o $@
+	$(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections $^ -o $@
 
 # Other targets
 
diff --git a/ld_so/ld_script/aarch64-unknown-redox.ld b/ld_so/ld_script/aarch64-unknown-redox.ld
index 1d08c36eb..b7ff6d7ed 100644
--- a/ld_so/ld_script/aarch64-unknown-redox.ld
+++ b/ld_so/ld_script/aarch64-unknown-redox.ld
@@ -98,25 +98,25 @@ SECTIONS
   .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
   .exception_ranges   : ONLY_IF_RW { *(.exception_ranges*) }
   /* Thread Local Storage sections  */
-  .tdata          :
+  /* .tdata          :
    {
      PROVIDE_HIDDEN (__tdata_start = .);
      *(.tdata .tdata.* .gnu.linkonce.td.*)
    }
-  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
+  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
   .preinit_array    :
   {
     PROVIDE_HIDDEN (__preinit_array_start = .);
     KEEP (*(.preinit_array))
     PROVIDE_HIDDEN (__preinit_array_end = .);
   }
-  .init_array    :
+  /* .init_array    :
   {
     PROVIDE_HIDDEN (__init_array_start = .);
     KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
     KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
     PROVIDE_HIDDEN (__init_array_end = .);
-  }
+  } */
   .fini_array    :
   {
     PROVIDE_HIDDEN (__fini_array_start = .);
@@ -241,5 +241,16 @@ SECTIONS
   .debug_macro    0 : { *(.debug_macro) }
   .debug_addr     0 : { *(.debug_addr) }
   .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
-  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
+  /DISCARD/ : { 
+    *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) 
+    /*
+     * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
+     * ld.so will be moved out of relibc. So, till that time, we have to discard any sections
+     * that may reference use thread local storage.
+     *
+     * .init_array also depends on TLS and is discarded as we don't need it.
+     */
+    *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
+    *(.init_array)
+  }
 }
diff --git a/ld_so/ld_script/i686-unknown-redox.ld b/ld_so/ld_script/i686-unknown-redox.ld
index f9badfd1d..8e4576a8d 100644
--- a/ld_so/ld_script/i686-unknown-redox.ld
+++ b/ld_so/ld_script/i686-unknown-redox.ld
@@ -98,25 +98,25 @@ SECTIONS
   .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
   .exception_ranges   : ONLY_IF_RW { *(.exception_ranges*) }
   /* Thread Local Storage sections  */
-  .tdata          :
+  /* .tdata          :
    {
      PROVIDE_HIDDEN (__tdata_start = .);
      *(.tdata .tdata.* .gnu.linkonce.td.*)
    }
-  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
+  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
   .preinit_array    :
   {
     PROVIDE_HIDDEN (__preinit_array_start = .);
     KEEP (*(.preinit_array))
     PROVIDE_HIDDEN (__preinit_array_end = .);
   }
-  .init_array    :
+  /* .init_array    :
   {
     PROVIDE_HIDDEN (__init_array_start = .);
     KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
     KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
     PROVIDE_HIDDEN (__init_array_end = .);
-  }
+  } */
   .fini_array    :
   {
     PROVIDE_HIDDEN (__fini_array_start = .);
@@ -241,5 +241,16 @@ SECTIONS
   .debug_macro    0 : { *(.debug_macro) }
   .debug_addr     0 : { *(.debug_addr) }
   .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
-  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
+  /DISCARD/ : { 
+    *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) 
+    /*
+     * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
+     * ld.so will be moved out of relibc. So, till that time, we have to discard any sections
+     * that may reference use thread local storage.
+     *
+     * .init_array also depends on TLS and is discarded as we don't need it.
+     */
+    *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
+    *(.init_array)
+  }
 }
diff --git a/ld_so/ld_script/riscv64gc-unknown-redox.ld b/ld_so/ld_script/riscv64gc-unknown-redox.ld
index 37fb2bb1e..cfbbc0cdf 100644
--- a/ld_so/ld_script/riscv64gc-unknown-redox.ld
+++ b/ld_so/ld_script/riscv64gc-unknown-redox.ld
@@ -89,25 +89,25 @@ SECTIONS
   .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
   .exception_ranges   : ONLY_IF_RW { *(.exception_ranges*) }
   /* Thread Local Storage sections  */
-  .tdata          :
+  /* .tdata          :
    {
      PROVIDE_HIDDEN (__tdata_start = .);
      *(.tdata .tdata.* .gnu.linkonce.td.*)
    }
-  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
+  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
   .preinit_array    :
   {
     PROVIDE_HIDDEN (__preinit_array_start = .);
     KEEP (*(.preinit_array))
     PROVIDE_HIDDEN (__preinit_array_end = .);
   }
-  .init_array    :
+  /* .init_array    :
   {
     PROVIDE_HIDDEN (__init_array_start = .);
     KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
     KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
     PROVIDE_HIDDEN (__init_array_end = .);
-  }
+  } */
   .fini_array    :
   {
     PROVIDE_HIDDEN (__fini_array_start = .);
@@ -232,5 +232,16 @@ SECTIONS
   .debug_macro    0 : { *(.debug_macro) }
   .debug_addr     0 : { *(.debug_addr) }
   .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
-  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
+  /DISCARD/ : { 
+    *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) 
+    /*
+     * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
+     * ld.so will be moved out of relibc. So, till that time, we have to discard any sections
+     * that may reference use thread local storage.
+     *
+     * .init_array also depends on TLS and is discarded as we don't need it.
+     */
+    *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
+    *(.init_array)
+  }
 }
diff --git a/ld_so/ld_script/x86_64-unknown-linux-gnu.ld b/ld_so/ld_script/x86_64-unknown-linux-gnu.ld
index c67cb55af..3aa0e0f47 100644
--- a/ld_so/ld_script/x86_64-unknown-linux-gnu.ld
+++ b/ld_so/ld_script/x86_64-unknown-linux-gnu.ld
@@ -101,27 +101,25 @@ SECTIONS
   .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
   .exception_ranges   : ONLY_IF_RW { *(.exception_ranges*) }
   /* Thread Local Storage sections  */
-  __relibc_ldso_tls_start = .;
-  .tdata          : ALIGN(4K)
+  /* .tdata          : ALIGN(4K)
    {
      PROVIDE_HIDDEN (__tdata_start = .);
      *(.tdata .tdata.* .gnu.linkonce.td.*)
    }
-  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
-  __relibc_ldso_tls_end = .;
+  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
   .preinit_array    :
   {
     PROVIDE_HIDDEN (__preinit_array_start = .);
     KEEP (*(.preinit_array))
     PROVIDE_HIDDEN (__preinit_array_end = .);
   }
-  .init_array    :
+  /* .init_array    :
   {
     PROVIDE_HIDDEN (__init_array_start = .);
     KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
     KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
     PROVIDE_HIDDEN (__init_array_end = .);
-  }
+  } */
   .fini_array    :
   {
     PROVIDE_HIDDEN (__fini_array_start = .);
@@ -246,5 +244,16 @@ SECTIONS
   .debug_macro    0 : { *(.debug_macro) }
   .debug_addr     0 : { *(.debug_addr) }
   .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
-  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
+  /DISCARD/ : { 
+    *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) 
+    /*
+     * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
+     * ld.so will be moved out of relibc. So, till that time, we have to discard any sections
+     * that may reference use thread local storage.
+     *
+     * .init_array also depends on TLS and is discarded as we don't need it.
+     */
+    *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
+    *(.init_array)
+  }
 }
diff --git a/ld_so/ld_script/x86_64-unknown-redox.ld b/ld_so/ld_script/x86_64-unknown-redox.ld
index 00df43509..975c833fb 100644
--- a/ld_so/ld_script/x86_64-unknown-redox.ld
+++ b/ld_so/ld_script/x86_64-unknown-redox.ld
@@ -98,27 +98,25 @@ SECTIONS
   .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
   .exception_ranges   : ONLY_IF_RW { *(.exception_ranges*) }
   /* Thread Local Storage sections  */
-  __relibc_ldso_tls_start = .;
-  .tdata          : ALIGN(4K)
+  /* .tdata          : ALIGN(4K)
    {
      PROVIDE_HIDDEN (__tdata_start = .);
      *(.tdata .tdata.* .gnu.linkonce.td.*)
    }
-  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
-  __relibc_ldso_tls_end = .;
+  .tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
   .preinit_array    :
   {
     PROVIDE_HIDDEN (__preinit_array_start = .);
     KEEP (*(.preinit_array))
     PROVIDE_HIDDEN (__preinit_array_end = .);
   }
-  .init_array    :
+  /* .init_array    :
   {
     PROVIDE_HIDDEN (__init_array_start = .);
     KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
     KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
     PROVIDE_HIDDEN (__init_array_end = .);
-  }
+  } */
   .fini_array    :
   {
     PROVIDE_HIDDEN (__fini_array_start = .);
@@ -243,5 +241,16 @@ SECTIONS
   .debug_macro    0 : { *(.debug_macro) }
   .debug_addr     0 : { *(.debug_addr) }
   .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
-  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
+  /DISCARD/ : { 
+    *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) 
+    /*
+     * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
+     * ld.so will be moved out of relibc. So, till that time, we have to discard any sections
+     * that may reference use thread local storage.
+     *
+     * .init_array also depends on TLS and is discarded as we don't need it.
+     */
+    *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
+    *(.init_array)
+  }
 }
diff --git a/ld_so/src/lib.rs b/ld_so/src/lib.rs
index 9ba54a23c..955d6b14c 100644
--- a/ld_so/src/lib.rs
+++ b/ld_so/src/lib.rs
@@ -45,8 +45,6 @@ _start:
     # Call ld_so_start(stack, entry)
     mov rdi, rbp
     sub rsi, 5
-    lea rdx, __relibc_ldso_tls_start
-    lea rcx, __relibc_ldso_tls_end
     call relibc_ld_so_start
 
     # Restore original stack, clear registers, and jump to new start function
@@ -83,7 +81,7 @@ pub unsafe extern "C" fn main(_argc: isize, _argv: *const *const i8) -> usize {
 
 #[linkage = "weak"]
 #[no_mangle]
-extern "C" fn relibc_panic(pi: &::core::panic::PanicInfo) -> ! {
+extern "C" fn relibc_panic(_pi: &::core::panic::PanicInfo) -> ! {
     loop {}
 }
 
diff --git a/src/header/netdb/mod.rs b/src/header/netdb/mod.rs
index d9d2f8103..e35a30fc3 100644
--- a/src/header/netdb/mod.rs
+++ b/src/header/netdb/mod.rs
@@ -989,7 +989,7 @@ pub extern "C" fn herror(prefix: *const c_char) {
         str::from_utf8_unchecked(msg_cstr.to_bytes())
     };
 
-    let mut writer = platform::FileWriter(2);
+    let mut writer = platform::FileWriter::new(2);
     // Prefix is optional
     match unsafe { CStr::from_nullable_ptr(prefix) }
         .and_then(|prefix| str::from_utf8(prefix.to_bytes()).ok())
diff --git a/src/header/stdio/mod.rs b/src/header/stdio/mod.rs
index d94b5f1d6..22bd933b4 100644
--- a/src/header/stdio/mod.rs
+++ b/src/header/stdio/mod.rs
@@ -852,7 +852,7 @@ pub unsafe extern "C" fn perror(s: *const c_char) {
     } else {
         "Unknown error"
     };
-    let mut w = platform::FileWriter(2);
+    let mut w = platform::FileWriter::new(2);
 
     // The prefix, `s`, is optional (empty or NULL) according to the spec
     match CStr::from_nullable_ptr(s).and_then(|s_cstr| str::from_utf8(s_cstr.to_bytes()).ok()) {
diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs
index 135ec59dc..b8322ba41 100644
--- a/src/ld_so/dso.rs
+++ b/src/ld_so/dso.rs
@@ -5,7 +5,7 @@ use super::{
 };
 use crate::{
     header::{errno::STR_ERROR, sys_mman},
-    platform::{types::c_void, ERRNO},
+    platform::{types::c_void, Pal, Sys, ERRNO},
 };
 use alloc::{
     collections::BTreeMap,
@@ -215,7 +215,7 @@ impl DSO {
                     flags |= sys_mman::MAP_FIXED_NOREPLACE;
                 }
                 trace!("  mmap({:#x}, {:x}, {:x})", start, size, flags);
-                let ptr = sys_mman::mmap(
+                let ptr = Sys::mmap(
                     start as *mut c_void,
                     size,
                     //TODO: Make it possible to not specify PROT_EXEC on Redox
@@ -223,16 +223,9 @@ impl DSO {
                     flags,
                     -1,
                     0,
-                );
-                if ptr as usize == !0
-                /* MAP_FAILED */
-                {
-                    return Err(Error::Malformed(format!(
-                        "failed to map {}. errno: {}",
-                        path,
-                        STR_ERROR[ERRNO.get() as usize]
-                    )));
-                }
+                )
+                .map_err(|e| Error::Malformed(format!("failed to map {}. errno: {}", path, e.0)))?;
+
                 if start as *mut c_void != ptr::null_mut() {
                     assert_eq!(
                         ptr, start as *mut c_void,
@@ -425,7 +418,7 @@ impl DSO {
 impl Drop for DSO {
     fn drop(&mut self) {
         self.run_fini();
-        unsafe { sys_mman::munmap(self.mmap.as_mut_ptr() as *mut c_void, self.mmap.len()) };
+        unsafe { Sys::munmap(self.mmap.as_mut_ptr() as *mut c_void, self.mmap.len()) };
     }
 }
 
diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs
index 998a3bdb7..9e15266c0 100644
--- a/src/ld_so/linker.rs
+++ b/src/ld_so/linker.rs
@@ -12,14 +12,17 @@ use goblin::{
 
 use crate::{
     c_str::{CStr, CString},
-    fs::File,
+    error::Errno,
     header::{
         dl_tls::{__tls_get_addr, dl_tls_index},
         fcntl, sys_mman,
         unistd::F_OK,
     },
-    io::Read,
-    platform::types::c_void,
+    io::{self, Read},
+    platform::{
+        types::{c_int, c_void},
+        Pal, Sys,
+    },
 };
 
 use super::{
@@ -31,6 +34,26 @@ use super::{
     ExpectTlsFree, PATH_SEP,
 };
 
+/// Same as [`crate::fs::File`], but does not touch [`crate::platform::ERRNO`] as the dynamic
+/// linker does not have thread-local storage.
+struct File {
+    fd: c_int,
+}
+
+impl File {
+    pub fn open(path: CStr, oflag: c_int) -> core::result::Result<Self, Errno> {
+        Ok(Self {
+            fd: Sys::open(path, oflag, 0)?,
+        })
+    }
+}
+
+impl io::Read for File {
+    fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, io::Error> {
+        Sys::read(self.fd, buf).map_err(|err| io::Error::from_raw_os_error(err.0))
+    }
+}
+
 #[derive(Clone, Copy, Debug)]
 pub struct Symbol {
     pub value: usize,
@@ -70,7 +93,6 @@ impl Linker {
         }
     }
 
-    /// **Warning**: Switches to the program's TCB. Thread locals should not be accessed after this.
     pub fn load_program(&mut self, path: &str, base_addr: Option<usize>) -> Result<usize> {
         self.load_object(path, &None, base_addr, false)?;
         return Ok(self.objects.get(&root_id).unwrap().entry_point);
@@ -193,10 +215,7 @@ impl Linker {
                 tcb.append_masters(tcb_masters);
                 // Copy the master data into the static TLS block.
                 tcb.copy_masters()?;
-
                 tcb.activate();
-                // XXX: Beyond this point, any thread local's for ld.so should not be accessed. Though, the
-                // TCB can still be accessed.
             } else {
                 let tcb = Tcb::current().expect("failed to get current tcb");
 
@@ -502,12 +521,10 @@ impl Linker {
                         vaddr as *const u8
                     };
                     trace!("  prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot);
-                    sys_mman::mprotect(ptr as *mut c_void, vsize, prot)
+                    Sys::mprotect(ptr as *mut c_void, vsize, prot).map_err(|_| {
+                        Error::Malformed(format!("failed to mprotect {}", obj.name))
+                    })?;
                 };
-
-                if res < 0 {
-                    return Err(Error::Malformed(format!("failed to mprotect {}", obj.name)));
-                }
             }
         }
 
diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs
index 67fb77081..5c695cf39 100644
--- a/src/ld_so/start.rs
+++ b/src/ld_so/start.rs
@@ -144,12 +144,6 @@ fn resolve_path_name(
     None
 }
 
-static mut LDSO_MASTER: Master = Master {
-    ptr: core::ptr::null_mut(),
-    len: 0,
-    offset: 0,
-};
-
 // TODO: Make unsafe
 #[no_mangle]
 pub extern "C" fn relibc_ld_so_start(
@@ -158,24 +152,9 @@ pub extern "C" fn relibc_ld_so_start(
     self_tls_start: usize,
     self_tls_end: usize,
 ) -> usize {
-    // Setup TLS and TCB for ourselves.
-    //
-    // On Redox, we need the TCB to setup in order to do anything. Also, thread local's like
-    // ERRNO may be accessed by the dynamic linker, so we need static TLS to be setup.
+    // Setup TCB for ourselves.
     unsafe {
-        let tls_size = (self_tls_end - self_tls_start).next_multiple_of(4096); // alignment set in linker script
-
-        let tcb = Tcb::new(tls_size).expect_notls("ld.so: failed to allocate bootstrap TCB");
-
-        LDSO_MASTER.ptr = self_tls_start as *mut u8;
-        LDSO_MASTER.len = self_tls_end - self_tls_start;
-        LDSO_MASTER.offset = tls_size;
-
-        tcb.masters_ptr = &mut LDSO_MASTER;
-        tcb.masters_len = core::mem::size_of::<Master>();
-
-        tcb.copy_masters()
-            .expect_notls("ld.so: failed to copy TLS master data");
+        let tcb = Tcb::new(0).expect_notls("ld.so: failed to allocate bootstrap TCB");
         tcb.activate();
 
         #[cfg(target_os = "redox")]
diff --git a/src/ld_so/tcb.rs b/src/ld_so/tcb.rs
index d26d4d0ae..8521842f4 100644
--- a/src/ld_so/tcb.rs
+++ b/src/ld_so/tcb.rs
@@ -241,19 +241,16 @@ impl Tcb {
 
     /// Mapping with correct flags for TCB and TLS
     unsafe fn map(size: usize) -> Result<&'static mut [u8]> {
-        let ptr = sys_mman::mmap(
+        let ptr = Sys::mmap(
             ptr::null_mut(),
             size,
             sys_mman::PROT_READ | sys_mman::PROT_WRITE,
             sys_mman::MAP_ANONYMOUS | sys_mman::MAP_PRIVATE,
             -1,
             0,
-        );
-        if ptr as usize == !0
-        /* MAP_FAILED */
-        {
-            return Err(Error::Malformed(format!("failed to map tls")));
-        }
+        )
+        .map_err(|_| Error::Malformed(format!("failed to map tls")))?;
+
         ptr::write_bytes(ptr as *mut u8, 0, size);
         Ok(slice::from_raw_parts_mut(ptr as *mut u8, size))
     }
diff --git a/src/lib.rs b/src/lib.rs
index d2697004d..4b30d18fe 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -78,7 +78,7 @@ static ALLOCATOR: Allocator = NEWALLOCATOR;
 pub extern "C" fn relibc_panic(pi: &::core::panic::PanicInfo) -> ! {
     use core::fmt::Write;
 
-    let mut w = platform::FileWriter(2);
+    let mut w = platform::FileWriter::new(2);
     let _ = w.write_fmt(format_args!("RELIBC PANIC: {}\n", pi));
 
     Sys::exit(1);
@@ -105,7 +105,7 @@ pub extern "C" fn rust_eh_personality() {}
 pub extern "C" fn rust_oom(layout: ::core::alloc::Layout) -> ! {
     use core::fmt::Write;
 
-    let mut w = platform::FileWriter(2);
+    let mut w = platform::FileWriter::new(2);
     let _ = w.write_fmt(format_args!(
         "RELIBC OOM: {} bytes aligned to {} bytes\n",
         layout.size(),
@@ -122,7 +122,7 @@ pub extern "C" fn rust_oom(layout: ::core::alloc::Layout) -> ! {
 pub extern "C" fn _Unwind_Resume() -> ! {
     use core::fmt::Write;
 
-    let mut w = platform::FileWriter(2);
+    let mut w = platform::FileWriter::new(2);
     let _ = w.write_str("_Unwind_Resume\n");
 
     Sys::exit(1);
diff --git a/src/macros.rs b/src/macros.rs
index 23f6809d1..a7ef0c8e6 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -13,7 +13,7 @@ macro_rules! c_str {
 macro_rules! print {
     ($($arg:tt)*) => ({
         use core::fmt::Write;
-        let _ = write!($crate::platform::FileWriter(1), $($arg)*);
+        let _ = write!($crate::platform::FileWriter::new(1), $($arg)*);
     });
 }
 
@@ -30,7 +30,7 @@ macro_rules! println {
 macro_rules! eprint {
     ($($arg:tt)*) => ({
         use core::fmt::Write;
-        let _ = write!($crate::platform::FileWriter(2), $($arg)*);
+        let _ = write!($crate::platform::FileWriter::new(2), $($arg)*);
     });
 }
 
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index 7d3ebc50c..92e3e042e 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -1,7 +1,7 @@
 //! Platform abstractions and environment.
 
 use crate::{
-    error::ResultExt,
+    error::{Errno, ResultExt},
     io::{self, Read, Write},
 };
 use alloc::{boxed::Box, vec::Vec};
@@ -86,13 +86,19 @@ impl<'a, W: WriteByte> WriteByte for &'a mut W {
     }
 }
 
-pub struct FileWriter(pub c_int);
+pub struct FileWriter(pub c_int, Option<Errno>);
 
 impl FileWriter {
-    pub fn write(&mut self, buf: &[u8]) -> isize {
-        Sys::write(self.0, buf)
-            .map(|u| u as isize)
-            .or_minus_one_errno()
+    pub fn new(fd: c_int) -> Self {
+        Self(fd, None)
+    }
+
+    pub fn write(&mut self, buf: &[u8]) -> fmt::Result {
+        let _ = Sys::write(self.0, buf).map_err(|err| {
+            self.1 = Some(err);
+            fmt::Error
+        })?;
+        Ok(())
     }
 }
 
-- 
GitLab