Index: uspace/lib/libc/include/errno.h
===================================================================
--- uspace/lib/libc/include/errno.h	(revision d2093d6216093b33fd00e516e5a5d3b907687613)
+++ uspace/lib/libc/include/errno.h	(revision 0fdd6bbb06ebd929c2d9e4c5f604650f5f06ef82)
@@ -48,4 +48,5 @@
 #define EXDEV		(-264)
 #define EIO		(-265)
+#define EMLINK		(-266)
 
 #endif
Index: uspace/srv/fs/fat/fat.h
===================================================================
--- uspace/srv/fs/fat/fat.h	(revision d2093d6216093b33fd00e516e5a5d3b907687613)
+++ uspace/srv/fs/fat/fat.h	(revision 0fdd6bbb06ebd929c2d9e4c5f604650f5f06ef82)
@@ -210,4 +210,5 @@
 extern fat_idx_t *fat_idx_get_by_index(dev_handle_t, fs_index_t);
 extern void fat_idx_destroy(fat_idx_t *);
+extern void fat_idx_hashin(fat_idx_t *);
 
 extern int fat_idx_init(void);
Index: uspace/srv/fs/fat/fat_dentry.c
===================================================================
--- uspace/srv/fs/fat/fat_dentry.c	(revision d2093d6216093b33fd00e516e5a5d3b907687613)
+++ uspace/srv/fs/fat/fat_dentry.c	(revision 0fdd6bbb06ebd929c2d9e4c5f604650f5f06ef82)
@@ -37,4 +37,5 @@
 
 #include "fat_dentry.h"
+#include <ctype.h>
 
 #define FAT_PAD			' ' 
@@ -45,5 +46,46 @@
 #define FAT_DENTRY_ERASED	0xe5
 
-void dentry_name_canonify(fat_dentry_t *d, char *buf)
+static bool is_d_char(const char ch)
+{
+	if (isalnum(ch) || ch == '_')
+		return true;
+	else
+		return false;
+}
+
+bool fat_dentry_name_verify(const char *name)
+{
+	unsigned i, dot;
+	bool dot_found = false;
+	
+
+	for (i = 0; name[i]; i++) {
+		if (name[i] == '.') {
+			if (dot_found) {
+				return false;
+			} else {
+				dot_found = true;
+				dot = i;
+			}
+		} else {
+			if (!is_d_char(name[i]))
+				return false;
+		}
+	}
+
+	if (dot_found) {
+		if (dot > FAT_NAME_LEN)
+			return false;
+		if (i - dot > FAT_EXT_LEN + 1)
+			return false;
+	} else {
+		if (i > FAT_NAME_LEN)
+			return false;
+	}
+
+	return true;
+}
+
+void fat_dentry_name_get(const fat_dentry_t *d, char *buf)
 {
 	int i;
@@ -72,5 +114,44 @@
 }
 
-fat_dentry_clsf_t fat_classify_dentry(fat_dentry_t *d)
+void fat_dentry_name_set(fat_dentry_t *d, const char *name)
+{
+	int i;
+	const char fake_ext[] = "   ";
+
+
+	for (i = 0; i < FAT_NAME_LEN; i++) {
+		switch ((uint8_t) *name) {
+		case 0xe5:
+			d->name[i] = FAT_DENTRY_E5_ESC;
+			name++;
+			break;
+		case '\0':
+		case '.':
+			d->name[i] = FAT_PAD;
+			break;
+		default:
+			d->name[i] = toupper(*name++);
+			break;
+		}
+	}
+	if (*name++ != '.')
+		name = fake_ext;
+	for (i = 0; i < FAT_EXT_LEN; i++) {
+		switch ((uint8_t) *name) {
+		case 0xe5:
+			d->ext[i] = FAT_DENTRY_E5_ESC;
+			name++;
+			break;
+		case '\0':
+			d->ext[i] = FAT_PAD;
+			break;
+		default:
+			d->ext[i] = toupper(*name++);
+			break;
+		}
+	}
+}
+
+fat_dentry_clsf_t fat_classify_dentry(const fat_dentry_t *d)
 {
 	if (d->attr & FAT_ATTR_VOLLABEL) {
@@ -80,5 +161,5 @@
 	if (d->name[0] == FAT_DENTRY_ERASED) {
 		/* not-currently-used entry */
-		return FAT_DENTRY_SKIP;
+		return FAT_DENTRY_FREE;
 	}
 	if (d->name[0] == FAT_DENTRY_UNUSED) {
Index: uspace/srv/fs/fat/fat_dentry.h
===================================================================
--- uspace/srv/fs/fat/fat_dentry.h	(revision d2093d6216093b33fd00e516e5a5d3b907687613)
+++ uspace/srv/fs/fat/fat_dentry.h	(revision 0fdd6bbb06ebd929c2d9e4c5f604650f5f06ef82)
@@ -35,4 +35,5 @@
 
 #include <stdint.h>
+#include <bool.h>
 
 #define FAT_NAME_LEN		8
@@ -46,4 +47,5 @@
 	FAT_DENTRY_SKIP,
 	FAT_DENTRY_LAST,
+	FAT_DENTRY_FREE,
 	FAT_DENTRY_VALID
 } fat_dentry_clsf_t;
@@ -71,6 +73,8 @@
 } __attribute__ ((packed)) fat_dentry_t;
 
-extern void dentry_name_canonify(fat_dentry_t *, char *);
-extern fat_dentry_clsf_t fat_classify_dentry(fat_dentry_t *);
+extern bool fat_dentry_name_verify(const char *);
+extern void fat_dentry_name_get(const fat_dentry_t *, char *);
+extern void fat_dentry_name_set(fat_dentry_t *, const char *);
+extern fat_dentry_clsf_t fat_classify_dentry(const fat_dentry_t *);
 
 #endif
Index: uspace/srv/fs/fat/fat_idx.c
===================================================================
--- uspace/srv/fs/fat/fat_idx.c	(revision d2093d6216093b33fd00e516e5a5d3b907687613)
+++ uspace/srv/fs/fat/fat_idx.c	(revision 0fdd6bbb06ebd929c2d9e4c5f604650f5f06ef82)
@@ -424,4 +424,17 @@
 }
 
+void fat_idx_hashin(fat_idx_t *idx)
+{
+	unsigned long pkey[] = {
+		[UPH_DH_KEY] = idx->dev_handle,
+		[UPH_PFC_KEY] = idx->pfc,
+		[UPH_PDI_KEY] = idx->pdi,
+	};
+
+	futex_down(&used_futex);
+	hash_table_insert(&up_hash, pkey, &idx->uph_link);
+	futex_up(&used_futex);
+}
+
 fat_idx_t *
 fat_idx_get_by_index(dev_handle_t dev_handle, fs_index_t index)
Index: uspace/srv/fs/fat/fat_ops.c
===================================================================
--- uspace/srv/fs/fat/fat_ops.c	(revision d2093d6216093b33fd00e516e5a5d3b907687613)
+++ uspace/srv/fs/fat/fat_ops.c	(revision 0fdd6bbb06ebd929c2d9e4c5f604650f5f06ef82)
@@ -344,5 +344,97 @@
 int fat_link(void *prnt, void *chld, const char *name)
 {
-	return ENOTSUP;	/* not supported at the moment */
+	fat_node_t *parentp = (fat_node_t *)prnt;
+	fat_node_t *childp = (fat_node_t *)chld;
+	fat_dentry_t *d;
+	fat_bs_t *bs;
+	block_t *b;
+	int i, j;
+	uint16_t bps;
+	unsigned dps;
+	unsigned blocks;
+
+	futex_down(&childp->lock);
+	if (childp->lnkcnt == 1) {
+		/*
+		 * On FAT, we don't support multiple hard links.
+		 */
+		futex_up(&childp->lock);
+		return EMLINK;
+	}
+	assert(childp->lnkcnt == 0);
+	futex_up(&childp->lock);
+
+	if (!fat_dentry_name_verify(name)) {
+		/*
+		 * Attempt to create unsupported name.
+		 */
+		return ENOTSUP;
+	}
+
+	/*
+	 * Get us an unused parent node's dentry or grow the parent and allocate
+	 * a new one.
+	 */
+	
+	futex_down(&parentp->idx->lock);
+	bs = block_bb_get(parentp->idx->dev_handle);
+	bps = uint16_t_le2host(bs->bps);
+	dps = bps / sizeof(fat_dentry_t);
+
+	blocks = parentp->size / bps;
+
+	for (i = 0; i < blocks; i++) {
+		b = fat_block_get(bs, parentp, i, BLOCK_FLAGS_NONE);
+		for (j = 0; j < dps; j++) {
+			d = ((fat_dentry_t *)b->data) + j;
+			switch (fat_classify_dentry(d)) {
+			case FAT_DENTRY_SKIP:
+			case FAT_DENTRY_VALID:
+				/* skipping used and meta entries */
+				continue;
+			case FAT_DENTRY_FREE:
+			case FAT_DENTRY_LAST:
+				/* found an empty slot */
+				goto hit;
+			}
+		}
+		block_put(b);
+	}
+	
+	/*
+	 * We need to grow the parent in order to create a new unused dentry.
+	 */
+	futex_up(&parentp->idx->lock);
+	return ENOTSUP;	/* XXX */
+
+hit:
+	/*
+	 * At this point we only establish the link between the parent and the
+	 * child.  The dentry, except of the name and the extension, will remain
+	 * uninitialized until the the corresponding node is synced. Thus the
+	 * valid dentry data is kept in the child node structure.
+	 */
+	memset(d, 0, sizeof(fat_dentry_t));
+	fat_dentry_name_set(d, name);
+	b->dirty = true;		/* need to sync block */
+	block_put(b);
+	futex_up(&parentp->idx->lock);
+
+	futex_down(&childp->idx->lock);
+	childp->idx->pfc = parentp->firstc;
+	childp->idx->pdi = i * dps + j;
+	futex_up(&childp->idx->lock);
+
+	futex_down(&childp->lock);
+	childp->lnkcnt = 1;
+	childp->dirty = true;		/* need to sync node */
+	futex_up(&childp->lock);
+
+	/*
+	 * Hash in the index structure into the position hash.
+	 */
+	fat_idx_hashin(childp->idx);
+
+	return EOK;
 }
 
@@ -375,4 +467,5 @@
 			switch (fat_classify_dentry(d)) {
 			case FAT_DENTRY_SKIP:
+			case FAT_DENTRY_FREE:
 				continue;
 			case FAT_DENTRY_LAST:
@@ -382,5 +475,5 @@
 			default:
 			case FAT_DENTRY_VALID:
-				dentry_name_canonify(d, name);
+				fat_dentry_name_get(d, name);
 				break;
 			}
@@ -465,4 +558,5 @@
 			switch (fat_classify_dentry(d)) {
 			case FAT_DENTRY_SKIP:
+			case FAT_DENTRY_FREE:
 				continue;
 			case FAT_DENTRY_LAST:
@@ -699,4 +793,5 @@
 				switch (fat_classify_dentry(d)) {
 				case FAT_DENTRY_SKIP:
+				case FAT_DENTRY_FREE:
 					continue;
 				case FAT_DENTRY_LAST:
@@ -705,5 +800,5 @@
 				default:
 				case FAT_DENTRY_VALID:
-					dentry_name_canonify(d, name);
+					fat_dentry_name_get(d, name);
 					block_put(b);
 					goto hit;
