diff --git a/Makefile b/Makefile
index bef596f..d90d690 100644
--- a/Makefile
+++ b/Makefile
@@ -22,8 +22,10 @@ LIB = \
 SRC = \
 	basename.c \
 	cat.c      \
+	chgrp.c    \
 	chmod.c    \
 	chown.c    \
+	chvt.c     \
 	cksum.c    \
 	cmp.c      \
 	cp.c       \
@@ -45,10 +47,13 @@ SRC = \
 	nl.c       \
 	nohup.c    \
 	paste.c    \
+	printenv.c \
 	pwd.c      \
 	rm.c       \
+	rmdir.c    \
 	sleep.c    \
 	sort.c     \
+	sync.c     \
 	tail.c     \
 	tee.c      \
 	test.c     \
@@ -57,6 +62,7 @@ SRC = \
 	tty.c      \
 	uname.c    \
 	uniq.c     \
+	unlink.c   \
 	seq.c      \
 	wc.c       \
 	yes.c
diff --git a/TODO b/TODO
index d6b9e0b..217829f 100644
--- a/TODO
+++ b/TODO
@@ -1,9 +1,5 @@
 cal [-1] [-3] [-y] [year]
 
-chgrp [-R] groupname file...
-
-chvt N
-
 comm [-123] file1 file2
 
 cut [-bcfs] [-d delim] list [file...]
@@ -26,33 +22,23 @@ id [-ruGgn] username
 
 md5sum [-c] [file...]
 
-nice [-n N] [command]
-
-printenv [variable...]
-
 printf [format] [data...]
 
 printf format [argument...]
 
 readlink [-fem] file
 
-rmdir [directory...]
-
 seq [-s string] [N [N]] N
 
 sha1sum [-c] [file...]
 
 split [-a N] [-b N] [-l N] [input [prefix]]
 
-sync
-
 test [expression...]
 
 tr string1 [string2]
 
 unexpand [-a] [-t N] [file...]
 
-unlink file
-
 who 
 
diff --git a/chgrp.1 b/chgrp.1
new file mode 100644
index 0000000..7196080
--- /dev/null
+++ b/chgrp.1
@@ -0,0 +1,22 @@
+.TH CHGRP 1 sbase\-VERSION
+.SH NAME
+nice \- invoke a utility with an altered nice value
+.SH SYNOPSIS
+.B chgrp
+.RB [ \-R ]
+.I groupname
+.I file...
+.SH DESCRIPTION
+.B chgrp
+sets the group id of the files specified by 
+.IR file
+to the gid of the group named
+.IR group.
+If the 
+.IR R
+flag is specified, this process is recursively applied to 
+everything in
+.IR file.
+
+.SH SEE ALSO
+.IR chown (1) chown (2) chmod (1) chmod (2) getgrnam (3)
diff --git a/chgrp.c b/chgrp.c
new file mode 100644
index 0000000..7bc0e86
--- /dev/null
+++ b/chgrp.c
@@ -0,0 +1,63 @@
+#include<stdio.h>
+#include<string.h>
+#include<unistd.h>
+#include<ftw.h>
+#include<errno.h>
+#include<grp.h>
+#include<sys/types.h>
+#include"util.h"
+
+int gid;
+int failures = 0;
+
+static void
+usage(){
+	eprintf("usage: chgrp [-R] groupname file...\n");
+}
+
+static int
+chgrp(const char *path, const struct stat *st, int f){
+	(void)f;
+	if(chown(path, st->st_uid, gid)==-1){
+		fprintf(stderr, "chgrp: '%s': %s\n", path, strerror(errno));
+		failures++;
+	}
+	return 0;
+}
+
+int
+main(int argc, char **argv){
+	int rflag = 0;
+	struct group *gr;
+	struct stat st;
+
+	ARGBEGIN {
+	case 'R':
+		rflag = 1;
+		break;
+	default:
+		usage();
+	} ARGEND;
+	if(argc<2)
+		usage();
+	gr = getgrnam(argv[0]);
+	if(!gr)
+		eprintf("chgrp: '%s': No such group\n", argv[0]);
+	gid = gr->gr_gid;
+
+	if(rflag){
+		while(*++argv)
+			ftw(*argv, chgrp, FOPEN_MAX);
+		return 0;
+	}
+	while(*++argv){
+		if(stat(*argv, &st)==-1){
+			fprintf(stderr, "chgrp: '%s': %s\n", *argv, strerror(errno));
+			failures++;
+			continue;
+		}
+		chgrp(*argv, &st, 0);
+	}
+	return failures;
+}
+
diff --git a/chvt.1 b/chvt.1
new file mode 100644
index 0000000..13a9be3
--- /dev/null
+++ b/chvt.1
@@ -0,0 +1,11 @@
+.TH CHVT 1 sbase\-VERSION
+.SH NAME
+chvt \- change foreground virtual terminal
+.SH SYNOPSIS
+.B chvt
+.I N
+.SH DESCRIPTION
+.B chvt
+brings /dev/ttyN to the foreground. This has the 
+same effect as Ctrl-Alt-FN.
+
diff --git a/chvt.c b/chvt.c
new file mode 100755
index 0000000..6af0024
--- /dev/null
+++ b/chvt.c
@@ -0,0 +1,55 @@
+#include<stdio.h>
+#include<stdlib.h>
+#include<string.h>
+#include<unistd.h>
+#include<fcntl.h>
+#include<sys/types.h>
+#include<sys/ioctl.h>
+#include"util.h"
+
+enum {
+	/* from <linux/vt.h> */
+	VT_ACTIVATE = 0x5606,
+	VT_WAITACTIVE = 0x5607,
+	/* from <linux/kd.h> */
+	KDGKBTYPE = 0x4B33,
+};
+
+static void
+usage(){
+	eprintf("usage: chvt N\n");
+}
+
+int
+main(int argc, char **argv){
+	int n, i, fd;
+	char c;
+	if(argc!=2 || strspn(argv[1], "1234567890")!=strlen(argv[1]))
+		usage();
+
+	n = atoi(argv[1]);
+	char *vts[] = {
+		"/proc/self/fd/0", 
+		"/dev/console",
+       		"/dev/tty",
+		"/dev/tty0",
+	};
+	for(i=0;i<LEN(vts);i++){
+		fd = open(vts[i], O_RDONLY);
+		if(fd<1)
+			continue;
+		c=0;
+		if(ioctl(fd, KDGKBTYPE, &c)==0)
+			goto VTfound;
+		close(fd);
+	}
+	eprintf("chvt: couldn't find a console.\n");
+VTfound:
+	if(ioctl(fd, VT_ACTIVATE, n)==-1)
+		eprintf("chvt: VT_ACTIVATE '%d':", n);
+	if(ioctl(fd, VT_WAITACTIVE, n)==-1)
+		eprintf("chvt: VT_WAITACTIVE '%d':", n);
+	close(fd);
+	return 0;
+}
+
diff --git a/printenv.1 b/printenv.1
new file mode 100644
index 0000000..4b42e40
--- /dev/null
+++ b/printenv.1
@@ -0,0 +1,19 @@
+.TH PRINTENV 1 sbase\-VERSION
+.SH NAME
+printenv \- print out the environment or the values of specific variables.
+.SH SYNOPSIS
+.B printenv
+.RB [ var... ]
+.SH DESCRIPTION
+.B printenv
+prints the entire environment as key=values pairs when
+no 
+.IR var
+is specified. Otherwise, in the order specified, 
+.B printenv
+prints the value only of each
+.IR var, 
+one per line.
+
+.SH SEE ALSO
+.IR env (1)
diff --git a/printenv.c b/printenv.c
new file mode 100644
index 0000000..546c01a
--- /dev/null
+++ b/printenv.c
@@ -0,0 +1,20 @@
+#include<stdio.h>
+#include<stdlib.h>
+
+extern char **environ;
+
+int
+main(int argc, char **argv){
+	char *var;
+	if(argc==1){
+		while(*environ)
+			printf("%s\n", *environ++);
+		return 0;
+	}
+	while(*++argv){
+		if((var=getenv(*argv)))
+			printf("%s\n", var);
+	}
+	return 0;
+}
+
diff --git a/rmdir.1 b/rmdir.1
new file mode 100644
index 0000000..db8411e
--- /dev/null
+++ b/rmdir.1
@@ -0,0 +1,25 @@
+.TH RMDIR  1 sbase\-VERSION
+.SH NAME
+rmdir \- remove a directory
+.SH SYNOPSIS
+.B rmdir
+.I directory...
+.SH DESCRIPTION
+.B rmdir
+attempts to remove all non-full directories specified
+by
+.IR directory.
+.SH BUGS
+Subdirectories are removed in the order specified, so
+.nf
+	rmdir foo/bar foo
+.fi
+will be successful, but
+.nf
+	rmdir foo foo/bar
+.fi
+will only succeed in removing 
+.BR foo/bar.
+
+.SH SEE ALSO
+.IR rm (1) rmdir (2) unlink (1) unlink (2) 
diff --git a/rmdir.c b/rmdir.c
new file mode 100644
index 0000000..a23cfe4
--- /dev/null
+++ b/rmdir.c
@@ -0,0 +1,22 @@
+#include<stdio.h>
+#include<string.h>
+#include<unistd.h>
+#include<errno.h>
+#include"util.h"
+
+static void
+usage(){
+	eprintf("usage: rmdir dir...\n");
+}
+
+int
+main(int argc, char **argv){
+	argv++;;
+	if(!*argv)
+		usage();
+	while(*argv){
+		if(rmdir(*argv++)==-1)
+			fprintf(stderr, "rmdir: '%s': %s\n", argv[-1], strerror(errno));
+	}	
+	return 0;
+}
diff --git a/sync.1 b/sync.1
new file mode 100644
index 0000000..efd4798
--- /dev/null
+++ b/sync.1
@@ -0,0 +1,14 @@
+.TH SYNC 1 sbase\-VERSION
+.SH NAME
+sync \- flush disk cache 
+.SH SYNOPSIS
+.B sync
+.SH DESCRIPTION
+.B sync
+invokes 
+.IR sync(2) 
+to flush all unwritten changes to the disk. This is 
+usually done before shutting down, rebooting or halting.
+
+.SH SEE ALSO
+.IR sync (2)  fsync (2)
diff --git a/sync.c b/sync.c
new file mode 100644
index 0000000..d773850
--- /dev/null
+++ b/sync.c
@@ -0,0 +1,17 @@
+#include<stdio.h>
+#include<unistd.h>
+#include"util.h"
+
+static void
+usage(){
+	eprintf("usage: sync\n");
+}
+
+int
+main(int argc, char **argv){
+	if(argc!=1)
+		usage();
+	sync(); 
+	return 0;
+}
+
diff --git a/unlink.1 b/unlink.1
new file mode 100644
index 0000000..fcc84fe
--- /dev/null
+++ b/unlink.1
@@ -0,0 +1,16 @@
+.TH UNLINK 1 sbase\-VERSION
+.SH NAME
+unlink \- call the unlink function
+.SH SYNOPSIS
+.B unlink
+.RB file
+.SH DESCRIPTION
+.B unlink
+calls the 
+.IR unlink
+function on
+.IR file.
+
+
+.SH SEE ALSO
+.IR unlink (2)
diff --git a/unlink.c b/unlink.c
new file mode 100644
index 0000000..0643e92
--- /dev/null
+++ b/unlink.c
@@ -0,0 +1,18 @@
+#include<unistd.h>
+#include"util.h"
+
+static void
+usage(){
+	eprintf("usage: unlink file\n");
+}
+
+int
+main(int argc, char **argv){
+	if(argc!=2)
+		usage();
+
+	if(unlink(argv[1])==-1)
+		eprintf("unlink: '%s':", argv[1]);
+
+	return 0;
+}
