diff --git a/src/header/regex/mod.rs b/src/header/regex/mod.rs
index da746884df9afee6a0f137f9662110693a7583f2..3742a34c7c4c76a9364086510ec43c423696e0c2 100644
--- a/src/header/regex/mod.rs
+++ b/src/header/regex/mod.rs
@@ -50,6 +50,7 @@ pub const REG_ESPACE: c_int = 13;
 pub const REG_BADRPT: c_int = 14;
 
 #[no_mangle]
+#[linkage = "weak"] // redefined in GIT
 pub extern "C" fn regcomp(out: *mut regex_t, pat: *const c_char, cflags: c_int) -> c_int {
     if cflags & REG_EXTENDED == REG_EXTENDED {
         return REG_ENOSYS;
@@ -84,7 +85,9 @@ pub extern "C" fn regcomp(out: *mut regex_t, pat: *const c_char, cflags: c_int)
         Err(_) => REG_BADPAT,
     }
 }
+
 #[no_mangle]
+#[linkage = "weak"] // redefined in GIT
 pub unsafe extern "C" fn regfree(regex: *mut regex_t) {
     Vec::from_raw_parts(
         (*regex).ptr as *mut Vec<(Token, Range)>,
@@ -92,7 +95,9 @@ pub unsafe extern "C" fn regfree(regex: *mut regex_t) {
         (*regex).capacity,
     );
 }
+
 #[no_mangle]
+#[linkage = "weak"] // redefined in GIT
 pub extern "C" fn regexec(
     regex: *const regex_t,
     input: *const c_char,
@@ -143,6 +148,7 @@ pub extern "C" fn regexec(
 }
 
 #[no_mangle]
+#[linkage = "weak"] // redefined in GIT
 pub extern "C" fn regerror(code: c_int, _regex: *const regex_t, out: *mut c_char, max: c_int) {
     let string = match code {
         0 => "No error\0",
diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs
index c10b0cd2198d47d424e8031b9a32b1dbc0ba481d..95dc1b0f559c705ede7ef06a68d34416b19047e9 100644
--- a/src/header/unistd/mod.rs
+++ b/src/header/unistd/mod.rs
@@ -445,7 +445,6 @@ pub extern "C" fn pwrite(
 
 #[no_mangle]
 pub extern "C" fn read(fildes: c_int, buf: *const c_void, nbyte: size_t) -> ssize_t {
-    use core::slice;
     let buf = unsafe { slice::from_raw_parts_mut(buf as *mut u8, nbyte as usize) };
     trace_expr!(
         Sys::read(fildes, buf),
@@ -456,9 +455,11 @@ pub extern "C" fn read(fildes: c_int, buf: *const c_void, nbyte: size_t) -> ssiz
     )
 }
 
-// #[no_mangle]
-pub extern "C" fn readlink(path: *const c_char, buf: *mut c_char, bufsize: size_t) -> c_int {
-    unimplemented!();
+#[no_mangle]
+pub extern "C" fn readlink(path: *const c_char, buf: *mut c_char, bufsize: size_t) -> ssize_t {
+    let path = unsafe { CStr::from_ptr(path) };
+    let buf = unsafe { slice::from_raw_parts_mut(buf as *mut u8, bufsize as usize) };
+    Sys::readlink(path, buf)
 }
 
 #[no_mangle]
@@ -518,9 +519,11 @@ pub extern "C" fn swab(src: *const c_void, dest: *mut c_void, nbytes: ssize_t) {
     unimplemented!();
 }
 
-// #[no_mangle]
+#[no_mangle]
 pub extern "C" fn symlink(path1: *const c_char, path2: *const c_char) -> c_int {
-    unimplemented!();
+    let path1 = unsafe { CStr::from_ptr(path1) };
+    let path2 = unsafe { CStr::from_ptr(path2) };
+    Sys::symlink(path1, path2)
 }
 
 // #[no_mangle]
diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs
index 3c221b2c41c4d0261315a4326de06fbc8498e63c..079b10f64532a19e19033e20e363f5be2a1c7278 100644
--- a/src/platform/linux/mod.rs
+++ b/src/platform/linux/mod.rs
@@ -305,19 +305,19 @@ impl Pal for Sys {
         e(unsafe { syscall!(READ, fildes, buf.as_mut_ptr(), buf.len()) }) as ssize_t
     }
 
-    fn realpath(pathname: &CStr, out: &mut [u8]) -> c_int {
-        fn readlink(pathname: &CStr, out: &mut [u8]) -> ssize_t {
-            e(unsafe {
-                syscall!(
-                    READLINKAT,
-                    AT_FDCWD,
-                    pathname.as_ptr(),
-                    out.as_mut_ptr(),
-                    out.len()
-                )
-            }) as ssize_t
-        }
+    fn readlink(pathname: &CStr, out: &mut [u8]) -> ssize_t {
+        e(unsafe {
+            syscall!(
+                READLINKAT,
+                AT_FDCWD,
+                pathname.as_ptr(),
+                out.as_mut_ptr(),
+                out.len()
+            )
+        }) as ssize_t
+    }
 
+    fn realpath(pathname: &CStr, out: &mut [u8]) -> c_int {
         let file = match File::open(pathname, fcntl::O_PATH) {
             Ok(file) => file,
             Err(_) => return -1,
@@ -332,7 +332,7 @@ impl Pal for Sys {
         proc_path.push(0);
 
         let len = out.len();
-        let read = readlink(
+        let read = Self::readlink(
             CStr::from_bytes_with_nul(&proc_path).unwrap(),
             &mut out[..len - 1],
         );
@@ -379,6 +379,10 @@ impl Pal for Sys {
         e(unsafe { syscall!(SETREUID, ruid, euid) }) as c_int
     }
 
+    fn symlink(path1: &CStr, path2: &CStr) -> c_int {
+        e(unsafe { syscall!(SYMLINKAT, path1.as_ptr(), AT_FDCWD, path2.as_ptr()) }) as c_int
+    }
+
     fn tcgetattr(fd: c_int, out: *mut termios) -> c_int {
         Self::ioctl(fd, TCGETS, out as *mut c_void)
     }
diff --git a/src/platform/pal/mod.rs b/src/platform/pal/mod.rs
index 217ff46d58a74fc57a6766769b615e0a46b99d32..547a9cd41f8e37a9e2fefed09cb1606cbca8256c 100644
--- a/src/platform/pal/mod.rs
+++ b/src/platform/pal/mod.rs
@@ -115,7 +115,7 @@ pub trait Pal {
 
     fn read(fildes: c_int, buf: &mut [u8]) -> ssize_t;
 
-    //fn readlink(pathname: &CStr, out: &mut [u8]) -> ssize_t;
+    fn readlink(pathname: &CStr, out: &mut [u8]) -> ssize_t;
 
     fn realpath(pathname: &CStr, out: &mut [u8]) -> c_int;
 
@@ -138,6 +138,8 @@ pub trait Pal {
 
     fn setreuid(ruid: uid_t, euid: uid_t) -> c_int;
 
+    fn symlink(path1: &CStr, path2: &CStr) -> c_int;
+
     fn tcgetattr(fd: c_int, out: *mut termios) -> c_int;
 
     fn tcsetattr(fd: c_int, act: c_int, value: *const termios) -> c_int;
diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs
index 4747dee4ea97f94925c9e11317d8f1715b2e5d32..fb7f81b77c4309a3e06256fb1835757f468affc0 100644
--- a/src/platform/redox/mod.rs
+++ b/src/platform/redox/mod.rs
@@ -835,6 +835,26 @@ impl Pal for Sys {
         e(syscall::read(fd as usize, buf)) as ssize_t
     }
 
+    fn readlink(pathname: &CStr, out: &mut [u8]) -> ssize_t {
+        let file = match File::open(pathname, fcntl::O_PATH | fcntl::O_SYMLINK) {
+            Ok(fd) => fd,
+            Err(_) => return -1,
+        };
+
+        if out.is_empty() {
+            return 0;
+        }
+
+        let len = out.len();
+        let read = e(syscall::fpath(*file as usize, &mut out[..len - 1]));
+        if (read as c_int) < 0 {
+            return -1;
+        }
+        out[read as usize] = 0;
+
+        0
+    }
+
     fn realpath(pathname: &CStr, out: &mut [u8]) -> c_int {
         let file = match File::open(pathname, fcntl::O_PATH) {
             Ok(fd) => fd,
@@ -1019,6 +1039,22 @@ impl Pal for Sys {
         e(syscall::setreuid(ruid as usize, euid as usize)) as c_int
     }
 
+    fn symlink(path1: &CStr, path2: &CStr) -> c_int {
+        let mut file = match File::open(
+            path2,
+            fcntl::O_CREAT | fcntl::O_WRONLY | fcntl::O_SYMLINK | 0o777,
+        ) {
+            Ok(fd) => fd,
+            Err(_) => return -1,
+        };
+
+        if file.write(path1.to_bytes()).is_err() {
+            return -1;
+        }
+
+        0
+    }
+
     fn tcgetattr(fd: c_int, out: *mut termios) -> c_int {
         let dup = e(syscall::dup(fd as usize, b"termios"));
         if dup == !0 {