diff --git a/src/header/grp/mod.rs b/src/header/grp/mod.rs
index 6c5b0c16d4d449ae4350535c0702985d1260b815..ea1efd2dc94a578e97e1b127769e82735f21edad 100644
--- a/src/header/grp/mod.rs
+++ b/src/header/grp/mod.rs
@@ -1,8 +1,79 @@
 //! grp implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/grp.h.html
 
-use crate::platform::types::*;
+use core::{
+    convert::{TryFrom, TryInto},
+    mem,
+    ops::{Deref, DerefMut},
+    pin::Pin,
+    primitive::str,
+    ptr, slice, num::ParseIntError, str::Matches,
+};
+
+use lazy_static::lazy_static;
+
+use alloc::{borrow::ToOwned, string::{String, FromUtf8Error}};
+use libc::strncmp;
+
+use crate::{
+    c_str::CStr,
+    fs::File,
+    header::{errno, fcntl, string::strlen},
+    io,
+    io::{prelude::*, BufReader, Lines},
+    platform::types::*,
+    platform,
+    sync::Mutex
+};
+
+use super::errno::*;
+
+#[derive(Clone, Copy, Debug)]
+struct DestBuffer {
+    ptr: *mut u8,
+    len: usize,
+}
+
+// Shamelessly stolen from pwd/mod.rs
+#[derive(Debug)]
+enum MaybeAllocated {
+    Owned(Pin<Box<[u8]>>),
+    Borrowed(DestBuffer),
+}
+impl Deref for MaybeAllocated {
+    type Target = [u8];
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            MaybeAllocated::Owned(boxed) => boxed,
+            MaybeAllocated::Borrowed(dst) => unsafe {
+                core::slice::from_raw_parts(dst.ptr, dst.len)
+            },
+        }
+    }
+}
+impl DerefMut for MaybeAllocated {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        match self {
+            MaybeAllocated::Owned(boxed) => boxed,
+            MaybeAllocated::Borrowed(dst) => unsafe {
+                core::slice::from_raw_parts_mut(dst.ptr, dst.len)
+            },
+        }
+    }
+}
+
+static mut GROUP_BUF: Option<MaybeAllocated> = None;
+static mut GROUP: group = group {
+    gr_name: ptr::null_mut(),
+    gr_passwd: ptr::null_mut(),
+    gr_gid: 0,
+    gr_mem: ptr::null_mut(),
+};
+
+lazy_static! { static ref LINE_READER: Mutex<Option<Lines<BufReader<File>>>> = Mutex::new(None); }
 
 #[repr(C)]
+#[derive(Debug)]
 pub struct group {
     pub gr_name: *mut c_char,
     pub gr_passwd: *mut c_char,
@@ -10,56 +81,264 @@ pub struct group {
     pub gr_mem: *mut *mut c_char,
 }
 
-// #[no_mangle]
+#[derive(Debug)]
+enum Error {
+    EOF,
+    SyntaxError,
+    BufTooSmall,
+    Misc(io::Error),
+    FromUtf8Error(FromUtf8Error),
+    ParseIntError(ParseIntError),
+    Other
+}
+
+#[derive(Debug)]
+struct OwnedGrp {
+    buffer: MaybeAllocated,
+    reference: group,
+}
+
+impl OwnedGrp {
+    fn into_global(self) -> *mut group {
+        unsafe {
+            GROUP_BUF = Some(self.buffer);
+            GROUP = self.reference;
+            &mut GROUP
+        }
+    }
+}
+
+fn split(buf: &mut [u8]) -> Option<group> {
+    let gid = match buf[0..mem::size_of::<gid_t>()].try_into() {
+        Ok(buf) => gid_t::from_ne_bytes(buf),
+        Err(err) => return None
+    };
+    
+    // We moved the gid to the beginning of the byte buffer so we can do this.
+    let mut parts = buf[mem::size_of::<gid_t>()..].split_mut(|&c| c == b'\0');
+    
+    Some(group {
+        gr_name: parts.next()?.as_mut_ptr() as *mut i8,
+        gr_passwd: parts.next()?.as_mut_ptr() as *mut i8,
+        gr_gid: gid,
+        gr_mem: parts.next()?.as_mut_ptr() as *mut *mut c_char
+        // this will work because this points to the first string, which also happens to be the start of the array. The two are equivalent, just need to by typecast.
+    })
+}
+
+fn parse_grp(line: String, destbuf: Option<DestBuffer>) -> Result<OwnedGrp, Error> {
+    let mut buffer = line.to_owned().into_bytes();
+
+    let mut buffer = buffer
+        .into_iter()
+        .map(|i| if i == b':' { b'\0' } else { i })
+        .chain([b'\0'])
+        .collect::<Vec<_>>();
+    let mut buffer = buffer
+        .split_mut(|i| *i == b'\0');
+
+    let mut gr_gid: gid_t = 0;
+    let strings = {
+        let mut vec: Vec<u8> = Vec::new();
+        
+        let gr_name = buffer.next().ok_or(Error::EOF)?.to_vec();
+        let gr_passwd = buffer.next().ok_or(Error::EOF)?.to_vec();
+        gr_gid = String::from_utf8(buffer.next().ok_or(Error::EOF)?.to_vec())
+            .map_err(|err| Error::FromUtf8Error(err))?
+            .parse::<gid_t>()
+            .map_err(|err| Error::ParseIntError(err))?;
+            
+        // Place the gid at the beginning of the byte buffer to make getting it back out again later, much faster.
+            
+        vec.extend(gr_gid.to_ne_bytes());
+        vec.extend(gr_name);
+        vec.push(0);
+        vec.extend(gr_passwd);
+        vec.push(0);
+        
+        for i in buffer.next().ok_or(Error::EOF)?
+            .split(|b| *b == b',')
+            .filter(|i| i.len() > 0) {
+            
+            vec.extend(i.to_vec());
+            vec.push(0);
+        }
+        
+        vec.extend(0usize.to_ne_bytes());
+        
+        vec
+    };
+    
+    let mut buffer = match destbuf {
+        None => MaybeAllocated::Owned(Box::into_pin(strings.into_boxed_slice())),
+        Some(buf) => {
+            let mut buf = MaybeAllocated::Borrowed(buf);
+            
+            if buf.len() < buf.len() {
+                unsafe { platform::errno = errno::ERANGE; }
+                return Err(Error::BufTooSmall);
+            }
+            
+            buf[..strings.len()].copy_from_slice(&strings);
+            buf
+        }
+    };
+    let reference = split(&mut buffer).ok_or(Error::Other)?;
+    
+    Ok(OwnedGrp {
+        buffer,
+        reference
+    })
+}
+
+#[no_mangle]
 pub extern "C" fn getgrgid(gid: gid_t) -> *mut group {
-    unimplemented!();
+    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return ptr::null_mut() };
+
+    for line in BufReader::new(db).lines() {
+        let Ok(line) = line else { return ptr::null_mut() };
+        let Ok(grp) = parse_grp(line, None) else { return ptr::null_mut() };
+        
+        if grp.reference.gr_gid == gid {
+            return grp.into_global();
+        }
+    }
+
+    return ptr::null_mut();
 }
 
-// #[no_mangle]
+#[no_mangle]
 pub extern "C" fn getgrnam(name: *const c_char) -> *mut group {
-    unimplemented!();
+    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return ptr::null_mut() };
+
+    for line in BufReader::new(db).lines() {
+        let Ok(line) = line else { return ptr::null_mut() };
+        
+        let Ok(grp) = parse_grp(line, None) else { return ptr::null_mut() };
+        
+        // Attempt to prevent BO vulnerabilities
+        if unsafe { strncmp(grp.reference.gr_name, name, strlen(grp.reference.gr_name).min(strlen(name))) > 0 } {
+            return grp.into_global();
+        }
+    }
+
+    return ptr::null_mut();
 }
 
-// #[no_mangle]
-pub extern "C" fn getgrgid_r(
-    gid: gid_t,
-    grp: *mut group,
-    buffer: *mut c_char,
-    bufsize: usize,
-    result: *mut *mut group,
-) -> c_int {
-    unimplemented!();
+#[no_mangle]
+pub extern "C" fn getgrgid_r(gid: gid_t, result_buf: *mut group, buffer: *mut c_char, buflen: usize, result: *mut *mut group) -> c_int {
+    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return ENOENT };
+
+    for line in BufReader::new(db).lines() {
+        let Ok(line) = line else { return EINVAL };
+        let Ok(mut grp) = parse_grp(line, Some(DestBuffer { ptr: buffer as *mut u8, len: buflen })) else { return EINVAL };
+
+        if grp.reference.gr_gid == gid {
+            unsafe {
+                *result_buf = grp.reference;
+                *result = result_buf;
+            };
+            
+            return 0;
+        }
+    }
+    
+    return ENOENT;
 }
 
-// #[no_mangle]
-pub extern "C" fn getgrnam_r(
-    name: *const c_char,
-    grp: *mut group,
-    buffer: *mut c_char,
-    bufsize: usize,
-    result: *mut *mut group,
-) -> c_int {
-    unimplemented!();
+#[no_mangle]
+pub extern "C" fn getgrnam_r(name: *const c_char, result_buf: *mut group, buffer: *mut c_char, buflen: usize, result: *mut *mut group) -> c_int {
+    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return ENOENT };
+
+    for line in BufReader::new(db).lines() {
+        let Ok(line) = line else { return EINVAL };
+        let Ok(mut grp) = parse_grp(line, Some(DestBuffer { ptr: buffer as *mut u8, len: buflen })) else { return EINVAL };
+
+        if unsafe { strncmp(grp.reference.gr_name, name, strlen(grp.reference.gr_name).min(strlen(name))) > 0 } {
+            unsafe {
+                *result_buf = grp.reference;
+                *result = result_buf;
+            };
+            
+            return 0;
+        }
+    }
+    
+    return ENOENT;
 }
 
-// #[no_mangle]
+#[no_mangle]
 pub extern "C" fn getgrent() -> *mut group {
-    unimplemented!();
+    let mut line_reader = LINE_READER.lock();
+    
+    if line_reader.is_none() {
+        let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return ptr::null_mut() };
+        *line_reader = Some(BufReader::new(db).lines());
+    }
+    
+    if let Some(lines) = line_reader.deref_mut() { 
+        let Some(line) = lines.next() else { return ptr::null_mut() };
+        let Ok(line) = line else { return ptr::null_mut() };
+        
+        if let Ok(grp) = parse_grp(line, None) {
+            return grp.into_global();
+        } else { return ptr::null_mut(); }
+        
+    } else {
+        return ptr::null_mut();
+    }
 }
 
-// #[no_mangle]
+#[no_mangle]
 pub extern "C" fn endgrent() {
-    unimplemented!();
+    let mut line_reader = LINE_READER.lock();
+    *line_reader = None;
 }
 
-// #[no_mangle]
+#[no_mangle]
 pub extern "C" fn setgrent() {
-    unimplemented!();
+    let mut line_reader = LINE_READER.lock();    
+    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return };
+    *line_reader = Some(BufReader::new(db).lines());
 }
 
-/*
 #[no_mangle]
-pub extern "C" fn func(args) -> c_int {
-    unimplemented!();
+pub extern "C" fn getgrouplist(user: *const c_char, group: gid_t, groups: *mut gid_t, ngroups: i32) -> i32 {
+    let mut grps = unsafe { Vec::<gid_t>::from_raw_parts(groups, 0, ngroups as usize) };
+    let Ok(usr) = (unsafe { crate::c_str::CStr::from_ptr(user).to_str() }) else { return 0 };
+
+    let Ok(db) = File::open(c_str!("/etc/group"), fcntl::O_RDONLY) else { return 0; };
+
+    for line in BufReader::new(db).lines() {
+        if grps.len() >= ngroups as usize {
+            return ngroups;
+        }
+
+        match line {
+            Err(_) => return 0,
+            Ok(line) => {
+                let mut parts = line.split(':');
+
+                let group_name = parts.next().unwrap_or("");
+                let group_password = parts.next().unwrap_or("");
+                let group_id = parts.next().unwrap_or("-1").parse::<i32>().unwrap();
+                let members = parts
+                    .next()
+                    .unwrap_or("")
+                    .split(",")
+                    .map(|i| i.trim())
+                    .collect::<Vec<_>>();
+
+                if members.iter().any(|i| *i == usr) {
+                    grps.push(group_id);
+                }
+            }
+        };
+    }
+
+    if grps.len() <= 0 {
+        grps.push(group);
+    }
+
+    return grps.len() as i32;
 }
-*/
diff --git a/src/lib.rs b/src/lib.rs
index a2c57c39d694513391597b20cea63680cfc856d4..fcddb73f0cdea430c738cde56a59cd0b1235b76b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,6 +14,7 @@
 #![feature(stmt_expr_attributes)]
 #![feature(str_internals)]
 #![feature(thread_local)]
+#![feature(vec_into_raw_parts)]
 #![allow(clippy::cast_lossless)]
 #![allow(clippy::cast_ptr_alignment)]
 #![allow(clippy::derive_hash_xor_eq)]
diff --git a/tests/Makefile b/tests/Makefile
index b9790c1eca596059843a5172e2b22301c48b71d0..47a54ada8fad63bdf5feedccf5f0711141070ab7 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -166,7 +166,11 @@ NAMES=\
 	pthread/barrier \
 	pthread/rwlock_trylock \
 	pthread/rwlock_randtest \
-	pthread/mutex_recursive
+	pthread/mutex_recursive \
+	grp/getgrouplist \
+	grp/getgrgid_r \
+	grp/getgrnam_r \
+	grp/gr_iter \
 #	resource/getrusage
 #	time/times
 
diff --git a/tests/grp/getgrgid_r.c b/tests/grp/getgrgid_r.c
new file mode 100644
index 0000000000000000000000000000000000000000..5792848d72380c5dd6d27d21d79f5ade159d341d
--- /dev/null
+++ b/tests/grp/getgrgid_r.c
@@ -0,0 +1,52 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <grp.h>
+
+bool test_getgrgid(gid_t gid) {
+    struct group* out = getgrgid(gid);
+    
+    if (out == NULL) {
+        printf("Did not find a group %d", gid);
+        return false;
+    }
+    
+    printf("getgrgid\n");
+    
+    char* start = out->gr_name;
+    int len = strlen(out->gr_name);
+    
+    printf("    %d = %s, GID: %d\n", gid, out->gr_name, out->gr_gid);
+    
+    return true;
+}
+
+bool test_getgrgid_r(gid_t gid) {
+    char* buf[100];
+    
+    struct group grp;
+    struct group* out = &grp;
+    struct group* tmp;
+    
+    int status = getgrgid_r(gid, out, buf, sizeof(buf), &tmp);
+    
+    if (out == NULL) {
+        printf("Did not find a group %d", gid);
+        return false;
+    }
+    
+    printf("getgrgid_r\n");   
+    
+    char* start = grp.gr_name;
+    int len = strlen(grp.gr_name);
+    
+    printf("    %d = %s, GID: %d\n", gid, grp.gr_name, grp.gr_gid);
+    
+    return true;
+}
+
+int main(void) {
+    test_getgrgid(1050);
+    test_getgrgid_r(1050);
+}
\ No newline at end of file
diff --git a/tests/grp/getgrnam_r.c b/tests/grp/getgrnam_r.c
new file mode 100644
index 0000000000000000000000000000000000000000..2f0a3ac615d5514567542802c5300bd2b227d857
--- /dev/null
+++ b/tests/grp/getgrnam_r.c
@@ -0,0 +1,52 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <grp.h>
+
+bool test_getgrnam(char* gr_name) {
+    struct group* out = getgrnam(gr_name);
+    
+    if (out == NULL) {
+        printf("Did not find a group '%s'", gr_name);
+        return false;
+    }
+    
+    printf("getgrnam\n");
+
+    char* start = out->gr_name;
+    int len = strlen(out->gr_name);
+    
+    printf("    '%s' = %d\n", gr_name, out->gr_gid);
+    
+    return true;
+}
+
+bool test_getgrnam_r(char* gr_name) {
+    char* buf[100];
+    
+    struct group grp;
+    struct group* out = &grp;
+    struct group* tmp;
+    
+    int status = getgrnam_r(gr_name, out, buf, sizeof(buf), &tmp);
+    
+    if (out == NULL) {
+        printf("Did not find a group '%s'", gr_name);
+        return false;
+    }
+    
+    printf("getgrnam_r\n");   
+    
+    char* start = grp.gr_name;
+    int len = strlen(grp.gr_name);
+
+    printf("    '%s' = %d\n", gr_name, out->gr_gid);
+    
+    return true;
+}
+
+int main(void) {
+    test_getgrnam("lcake");
+    test_getgrnam_r("lcake");
+}
\ No newline at end of file
diff --git a/tests/grp/getgrouplist.c b/tests/grp/getgrouplist.c
new file mode 100644
index 0000000000000000000000000000000000000000..61e11d1a9aee853d87bd22d4fac5343404d60038
--- /dev/null
+++ b/tests/grp/getgrouplist.c
@@ -0,0 +1,15 @@
+#include <stdio.h>
+#include <grp.h>
+
+int main(void) {
+    gid_t groups[20];
+    int ngroup = 20;
+    int num_groups = getgrouplist("user", 1000, groups, &ngroup);
+    
+    printf("Num Groups: %d\n", num_groups);
+    
+    for (int i = 0; i < num_groups; i++)
+        printf("i: %d\n", groups[i]);
+    
+    return 0;
+}
\ No newline at end of file
diff --git a/tests/grp/gr_iter.c b/tests/grp/gr_iter.c
new file mode 100644
index 0000000000000000000000000000000000000000..dd7e59984feb35abcc3e48916516ef8be52f8482
--- /dev/null
+++ b/tests/grp/gr_iter.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+#include <grp.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+    printf("getgrent\n");
+    
+    for (struct group* grp = getgrent(); grp != NULL; grp = getgrent())
+        printf("    %s = %d\n", grp->gr_name, grp->gr_gid);
+}
\ No newline at end of file