diff --git a/Makefile b/Makefile
index f825ec4..38f4efa 100644
--- a/Makefile
+++ b/Makefile
@@ -68,6 +68,7 @@ SRC = \
 	sponge.c   \
 	sync.c     \
 	tail.c     \
+	tar.c      \
 	tee.c      \
 	test.c     \
 	touch.c    \
diff --git a/tar.1 b/tar.1
new file mode 100644
index 0000000..414ca4b
--- /dev/null
+++ b/tar.1
@@ -0,0 +1,27 @@
+.TH TAR 1 sbase\-VERSION
+.SH NAME
+tar \- create, list or extract a tape archive
+.SH SYNOPSIS
+.B tar
+.BR c | t | x
+.RI [ path ]
+.SH DESCRIPTION
+.B tar 
+is the standard file archiver. Generally the archices
+created with it are further compressed.
+.SH OPTIONS
+.TP
+.B x
+extract tarball from stdin
+.TP
+.B t
+list all files in tarball from stdin
+.TP
+.BI c\  path 
+creates tarball from 
+.I path 
+and prints it to stdout
+.SH SEE ALSO
+.IR ar (1)
+.IR gzip (1)
+.IR bzip2 (1)
diff --git a/tar.c b/tar.c
new file mode 100644
index 0000000..28caca8
--- /dev/null
+++ b/tar.c
@@ -0,0 +1,301 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <ftw.h>
+#include <grp.h>
+#include <pwd.h>
+#include "util.h"
+
+typedef struct Header Header;
+struct Header {
+	char name[100];
+	char mode[8];
+	char uid[8];
+	char gid[8];
+	char size[12];
+	char mtime[12];
+	char chksum[8];
+	char type;
+	char link[100];
+	char magic[6];
+	char version[2];
+	char uname[32];
+	char gname[32];
+	char major[8];
+	char minor[8];
+};
+
+enum {
+	Blksiz = 512
+};
+
+enum Type {
+	REG = '0', HARDLINK = '1', SYMLINK = '2', CHARDEV = '3', 
+	BLOCKDEV = '4', DIRECTORY = '5', FIFO = '6' 
+};
+
+static void putoctal(char *, unsigned, int);
+static int strlcpy(char *, const char *, int n);
+static int archive(const char *, const struct stat *, int);
+static int unarchive(char *, int, char[Blksiz]);
+static int print(char *, int , char[Blksiz]);
+static void c(char *);
+static void xt(int (*)(char*, int, char[Blksiz]));
+
+static FILE *tarfile;
+
+static void 
+usage(void)
+{
+	eprintf("usage: tar [-f tarfile] [-C dir] [-]x|t\n"
+	        "       tar [-f tarfile] [-C dir] [-]c dir\n"
+	        "       tar [-C dir] cf tarfile dir\n"
+	        "       tar [-C dir] x|tf tarfile\n");
+}
+
+int 
+main(int argc, char *argv[])
+{
+	char *file, *dir, *ap;
+	int flg['y'] = {0};
+
+	ARGBEGIN {
+	case 'x':
+	case 'c':
+	case 't':
+		flg[(unsigned)ARGC()] = 1;
+		break;
+	case 'C':
+		dir = EARGF(usage());
+		break;
+	case 'f':
+		file = EARGF(usage());
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	if(flg['x'] + flg['c'] + flg['t'] > 1)
+		usage();
+	if(flg['x'] + flg['c'] + flg['t'])
+		goto Action;
+
+	if(argc < 1)
+		usage();
+
+	for(ap = argv[0]; *ap; ap++) {
+		switch(*ap){
+		case 'x':
+		case 'c':
+		case 't':
+			flg[(unsigned)*ap] = 1;
+			break;
+		case 'f':
+			if(argc < 2)
+				usage();
+			argc--, argv++;
+			file = argv[0];
+			break;
+		case 'C':
+			if(argc < 2)
+				usage();
+			argc--, argv++;
+			dir = argv[0];
+			break;
+		default:
+			usage();
+		}
+	}
+	argc--, argv++;
+	if(flg['x']+ flg['c']+ flg['t'] != 1)
+		usage();
+
+Action:
+	if(file) {
+		tarfile = fopen(file, flg['c'] ? "wb" : "rb");
+		if(!tarfile)
+			eprintf("tar: open '%s':", file);
+	} else {
+		tarfile = flg['c'] ? stdout : stdin;
+	}
+
+	if(dir)
+		chdir(dir);
+
+	if(flg['c']){
+		if(argc != 1)
+			usage();
+		c(argv[0]);
+	} else if (flg['x'] || flg['t']){
+		if(argc != 0)
+			usage();
+		xt(flg['x'] ? unarchive : print);
+	}
+
+	return 0;
+}
+
+void
+putoctal(char *dst, unsigned num, int n)
+{
+	snprintf(dst, n, "%.*o", n-1, num);
+}
+
+int 
+strlcpy(char *dst, const char *src, int n)
+{
+	return snprintf(dst, n, "%s", src);
+}
+
+int 
+archive(const char* path, const struct stat* sta, int type)
+{
+	unsigned char b[Blksiz];
+	unsigned chksum;
+	int l, x;
+	Header *h = (void*)b;
+	FILE *f = NULL;
+	struct stat st;
+	struct passwd *pw;
+	struct group *gr;
+	mode_t mode;
+
+	lstat(path, &st);
+	pw = getpwuid(st.st_uid);
+	gr = getgrgid(st.st_gid);
+
+	memset(b, 0, sizeof b);
+	strlcpy (h->name,  path,                      sizeof h->name);
+	putoctal(h->mode,  (unsigned)st.st_mode&0777, sizeof h->mode);
+	putoctal(h->uid,   (unsigned)st.st_uid,       sizeof h->uid);
+	putoctal(h->gid,   (unsigned)st.st_gid,       sizeof h->gid);
+	putoctal(h->size,  0,                         sizeof h->size);
+	putoctal(h->mtime, (unsigned)st.st_mtime,     sizeof h->mtime);
+	memcpy(h->magic,   "ustar",                   sizeof h->magic);
+	memcpy(h->version, "00",                      sizeof h->version);
+	strlcpy(h->uname,  pw->pw_name,               sizeof h->uname);
+	strlcpy(h->gname,  gr->gr_name,               sizeof h->gname);
+
+	mode = st.st_mode;
+	if(S_ISREG(mode)) {
+		h->type = REG;
+		putoctal(h->size, (unsigned)st.st_size,  sizeof h->size);
+		f = fopen(path, "r");
+	} else if(S_ISDIR(mode)) {
+		h->type = DIRECTORY;
+	} else if(S_ISLNK(mode)) {
+		h->type = SYMLINK;
+		readlink(path, h->link, (sizeof h->link)-1);
+	} else if(S_ISCHR(mode) || S_ISBLK(mode)) {
+		h->type = S_ISCHR(mode) ? CHARDEV : BLOCKDEV;
+		putoctal(h->major, (unsigned)major(st.st_dev), sizeof h->major);
+		putoctal(h->minor, (unsigned)minor(st.st_dev), sizeof h->minor);
+	} else if(S_ISFIFO(mode)) {
+		h->type = FIFO;
+	}
+
+	memset(h->chksum, ' ', sizeof h->chksum);
+	for(x = 0, chksum = 0; x < sizeof *h; x++)
+		chksum += b[x];
+	putoctal(h->chksum, chksum, sizeof h->chksum);
+
+	fwrite(b, Blksiz, 1, tarfile);
+	if(!f)
+		return 0;
+	while((l = fread(b, 1, Blksiz, f)) > 0) {
+		if(l < Blksiz)
+			memset(b+l, 0, Blksiz-l);
+		fwrite(b, Blksiz, 1, tarfile);
+	}
+	fclose(f);
+	return 0;	
+}
+
+int 
+unarchive(char *fname, int l, char b[Blksiz])
+{
+	char lname[101];
+	FILE *f = NULL;
+	unsigned long  mode, major, minor, type;
+	Header *h = (void*)b;
+
+	unlink(fname);
+	switch(h->type) {
+	case REG:
+		mode = strtoul(h->mode, 0, 8);
+		if(!(f = fopen(fname, "w")) || chmod(fname, mode))
+			perror(fname);
+		break;
+	case HARDLINK:
+	case SYMLINK:
+		strlcpy(lname, h->link, sizeof lname);
+		if(!((h->type == HARDLINK) ? link : symlink)(lname, fname))
+			perror(fname);
+		break;
+	case DIRECTORY:
+		mode = strtoul(h->mode, 0, 8);
+		if(mkdir(fname, (mode_t)mode))
+			perror(fname);
+		break;
+	case CHARDEV:
+	case BLOCKDEV:
+		mode = strtoul(h->mode, 0, 8);
+		major = strtoul(h->major, 0, 8);
+		minor = strtoul(h->mode, 0, 8);
+		type = (h->type == CHARDEV) ? S_IFCHR : S_IFBLK;
+		if(mknod(fname, type | mode, makedev(major, minor)))
+			perror(fname);
+		break;
+	case FIFO:
+		mode = strtoul(h->mode, 0, 8);
+		if(mknod(fname, S_IFIFO | mode, 0))
+			perror(fname);
+		break;
+	default:
+		fprintf(stderr, "usupported tarfiletype %c\n", h->type);
+	}
+	if(getuid() == 0 && chown(fname, strtoul(h->uid, 0, 8), 
+	                                 strtoul(h->gid, 0, 8)))
+		perror(fname);
+
+	for(; l > 0; l -= Blksiz) {
+		fread(b, Blksiz, 1, tarfile);
+		if(f)
+			fwrite(b, MIN(l, 512), 1, f);
+	}
+	if(f)
+		fclose(f);
+	return 0;
+}
+
+int 
+print(char * fname, int l, char b[Blksiz])
+{
+	puts(fname);
+	for(; l > 0; l -= Blksiz)
+		fread(b, Blksiz, 1, tarfile);
+	return 0;
+}
+
+void
+c(char * dir)
+{
+	ftw(dir, archive, FOPEN_MAX);
+}
+
+void
+xt(int (*fn)(char*, int, char[Blksiz]))
+{
+	char b[Blksiz], fname[101];
+	Header *h = (void*)b;
+
+	while(fread(b, Blksiz, 1, tarfile) && h->name[0] != '\0') {
+		strlcpy(fname, h->name, sizeof fname);
+		fn(fname, strtol(h->size, 0, 8), b);
+	}
+}
