diff --git a/src/stdio/src/printf.rs b/src/stdio/src/printf.rs
index 54ce1f7644b09754a2a799585a691c7be626f165..093ee82c122d6f902e00d8f9c05f2653dcdbc432 100644
--- a/src/stdio/src/printf.rs
+++ b/src/stdio/src/printf.rs
@@ -85,6 +85,13 @@ pub unsafe fn printf<W: fmt::Write>(mut w: W, format: *const c_char, mut ap: VaL
 
                     found_percent = false;
                 }
+                'o' => {
+                    let a = ap.get::<c_uint>();
+
+                    w.write_fmt(format_args!("{:o}", a));
+
+                    found_percent = false;
+                }
                 '-' => {}
                 '+' => {}
                 ' ' => {}
diff --git a/src/stdlib/src/lib.rs b/src/stdlib/src/lib.rs
index 1bc1ff4e46d4c14099e53e5b8490f11eeb8f3fac..dea8dbd13a4b17def216925f39324048faca1c66 100644
--- a/src/stdlib/src/lib.rs
+++ b/src/stdlib/src/lib.rs
@@ -416,8 +416,192 @@ pub unsafe extern "C" fn strtod(s: *const c_char, endptr: *mut *mut c_char) -> c
 }
 
 #[no_mangle]
-pub extern "C" fn strtol(s: *const c_char, endptr: *mut *mut c_char, base: c_int) -> c_long {
-    unimplemented!();
+pub unsafe extern "C" fn strtol(
+    s: *const c_char,
+    endptr: *mut *const c_char,
+    base: c_int,
+) -> c_long {
+    let set_endptr = |idx: isize| {
+        if !endptr.is_null() {
+            *endptr = s.offset(idx);
+        }
+    };
+
+    let invalid_input = || {
+        platform::errno = EINVAL;
+        set_endptr(0);
+    };
+
+    // only valid bases are 2 through 36
+    if base != 0 && (base < 2 || base > 36) {
+        invalid_input();
+        return 0;
+    }
+
+    let mut idx = 0;
+
+    // skip any whitespace at the beginning of the string
+    while ctype::isspace(*s.offset(idx) as c_int) != 0 {
+        idx += 1;
+    }
+
+    // check for +/-
+    let positive = match is_positive(*s.offset(idx)) {
+        Some((pos, i)) => {
+            idx += i;
+            pos
+        }
+        None => {
+            invalid_input();
+            return 0;
+        }
+    };
+
+    // convert the string to a number
+    let num_str = s.offset(idx);
+    let res = match base {
+        0 => detect_base(num_str).and_then(|(base, i)| convert_integer(num_str.offset(i), base)),
+        8 => convert_octal(num_str),
+        16 => convert_hex(num_str),
+        _ => convert_integer(num_str, base),
+    };
+
+    // check for error parsing octal/hex prefix
+    // also check to ensure a number was indeed parsed
+    let (num, i, _) = match res {
+        Some(res) => res,
+        None => {
+            invalid_input();
+            return 0;
+        }
+    };
+    idx += i;
+
+    // account for the sign
+    let mut num = num as c_long;
+    num = if num.is_negative() {
+        platform::errno = ERANGE;
+        if positive {
+            c_long::max_value()
+        } else {
+            c_long::min_value()
+        }
+    } else {
+        if positive {
+            num
+        } else {
+            -num
+        }
+    };
+
+    set_endptr(idx);
+
+    num
+}
+
+fn is_positive(ch: c_char) -> Option<(bool, isize)> {
+    match ch {
+        0 => None,
+        ch if ch == b'+' as c_char => Some((true, 1)),
+        ch if ch == b'-' as c_char => Some((false, 1)),
+        _ => Some((true, 0)),
+    }
+}
+
+fn detect_base(s: *const c_char) -> Option<(c_int, isize)> {
+    let first = unsafe { *s } as u8;
+    match first {
+        0 => None,
+        b'0' => {
+            let second = unsafe { *s.offset(1) } as u8;
+            if second == b'X' || second == b'x' {
+                Some((16, 2))
+            } else if second >= b'0' && second <= b'7' {
+                Some((8, 1))
+            } else {
+                // in this case, the prefix (0) is going to be the number
+                Some((8, 0))
+            }
+        }
+        _ => Some((10, 0)),
+    }
+}
+
+unsafe fn convert_octal(s: *const c_char) -> Option<(c_ulong, isize, bool)> {
+    if *s != 0 && *s == b'0' as c_char {
+        if let Some((val, idx, overflow)) = convert_integer(s.offset(1), 8) {
+            Some((val, idx + 1, overflow))
+        } else {
+            // in case the prefix is not actually a prefix
+            Some((0, 1, false))
+        }
+    } else {
+        None
+    }
+}
+
+unsafe fn convert_hex(s: *const c_char) -> Option<(c_ulong, isize, bool)> {
+    if (*s != 0 && *s == b'0' as c_char)
+        && (*s.offset(1) != 0 && (*s.offset(1) == b'x' as c_char || *s.offset(1) == b'X' as c_char))
+    {
+        convert_integer(s.offset(2), 16).map(|(val, idx, overflow)| (val, idx + 2, overflow))
+    } else {
+        None
+    }
+}
+
+fn convert_integer(s: *const c_char, base: c_int) -> Option<(c_ulong, isize, bool)> {
+    // -1 means the character is invalid
+    #[cfg_attr(rustfmt, rustfmt_skip)]
+    const LOOKUP_TABLE: [c_long; 256] = [
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+        25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+        25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    ];
+
+    let mut num: c_ulong = 0;
+    let mut idx = 0;
+    let mut overflowed = false;
+
+    loop {
+        let val = unsafe { LOOKUP_TABLE[*s.offset(idx) as usize] };
+        if val == -1 || val as c_int >= base {
+            break;
+        } else {
+            if let Some(res) = num.checked_mul(base as c_ulong)
+                .and_then(|num| num.checked_add(val as c_ulong))
+            {
+                num = res;
+            } else {
+                unsafe {
+                    platform::errno = ERANGE;
+                }
+                num = c_ulong::max_value();
+                overflowed = true;
+            }
+
+            idx += 1;
+        }
+    }
+
+    if idx > 0 {
+        Some((num, idx, overflowed))
+    } else {
+        None
+    }
 }
 
 #[no_mangle]
diff --git a/tests/.gitignore b/tests/.gitignore
index 7b70bccffc40d658e29d3a82098cafef75130ba3..5e35eadf6c6a60435f337ce970df0445cb12a649 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -23,4 +23,5 @@
 /pipe
 /printf
 /rmdir
+/stdlib/strtol
 /write
diff --git a/tests/Makefile b/tests/Makefile
index 2dc88010f0d2f17e3a1ae3d107282571123b84af..d1c3d111cb572bb29071f8f3624dcf2150be7f24 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -19,6 +19,7 @@ BINS=\
 	rmdir \
 	pipe \
 	printf \
+	stdlib/strtol \
 	write
 
 all: $(BINS)
@@ -39,6 +40,7 @@ expected: $(BINS)
 	for bin in $(BINS); \
 	do \
 		echo "# $${bin} #"; \
+		mkdir -p expected/`dirname $${bin}`; \
 		"./$${bin}" test args > "expected/$${bin}.stdout" 2> "expected/$${bin}.stderr"; \
 	done
 
@@ -48,6 +50,7 @@ verify: $(BINS)
 	for bin in $(BINS); \
 	do \
 		echo "# $${bin} #"; \
+		mkdir -p gen/`dirname $${bin}`; \
 		"./$${bin}" test args > "gen/$${bin}.stdout" 2> "gen/$${bin}.stderr"; \
 		diff -u "gen/$${bin}.stdout" "expected/$${bin}.stdout"; \
 		diff -u "gen/$${bin}.stderr" "expected/$${bin}.stderr"; \
diff --git a/tests/stdlib/strtol.c b/tests/stdlib/strtol.c
new file mode 100644
index 0000000000000000000000000000000000000000..7d09883c64bb0daaa992ccbb3a7bab5eaec7e13b
--- /dev/null
+++ b/tests/stdlib/strtol.c
@@ -0,0 +1,30 @@
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+int main(int argc, char* argv[]) {
+    printf("%ld\n", strtol("         -42", NULL, 0));
+    printf("%ld\n", strtol(" +555", NULL, 0));
+    printf("%ld\n", strtol("   1234567890    ", NULL, 0));
+
+    printf("%ld\n", strtol("         -42", NULL, 10));
+    printf("%ld\n", strtol(" +555", NULL, 10));
+    printf("%ld\n", strtol("   1234567890    ", NULL, 10));
+
+    printf("%lx\n", strtol("  0x38Acfg", NULL, 0));
+    printf("%lx\n", strtol("0Xabcdef12", NULL, 16));
+
+    printf("%lo\n", strtol("  073189", NULL, 0));
+    printf("%lo\n", strtol("     073189", NULL, 8));
+
+    printf("%lo\n", strtol("  0b", NULL, 8));
+    if(errno != 0) {
+        printf("errno is not 0 (%d), something went wrong\n", errno);
+    }
+    printf("%lo\n", strtol("  0b", NULL, 0));
+    if(errno != 0) {
+        printf("errno is not 0 (%d), something went wrong\n", errno);
+    }
+
+    return 0;
+}