diff -Nru linux-old/fs/fat/dir.c linux-new/fs/fat/dir.c
--- linux-old/fs/fat/dir.c	Fri Oct 12 16:48:42 2001
+++ linux-new/fs/fat/dir.c	Tue Jun 11 17:02:45 2002
@@ -179,6 +179,13 @@
 	return len;
 }
 
+static int is_symlink(char *extension)
+{
+	if (strncmp(extension, "LNK", 3) == 0)
+		return 1;
+	return 0;
+}
+
 /*
  * Return values: negative -> error, 0 -> not found, positive -> found,
  * value is the total amount of slots, including the shortname entry.
@@ -197,6 +204,7 @@
 	char work[8], bufname[260];	/* 256 + 4 */
 	int uni_xlate = MSDOS_SB(sb)->options.unicode_xlate;
 	int utf8 = MSDOS_SB(sb)->options.utf8;
+	int showsymlinks = MSDOS_SB(sb)->options.symlinks;
 	unsigned short opt_shortname = MSDOS_SB(sb)->options.shortname;
 	int ino, chl, i, j, last_u, res = 0;
 	loff_t cpos = 0;
@@ -319,6 +327,9 @@
 		xlate_len = utf8
 			?utf8_wcstombs(bufname, bufuname, sizeof(bufname))
 			:uni16_to_x8(bufname, bufuname, uni_xlate, nls_io);
+		if (showsymlinks && (xlate_len == name_len + 4)
+					&& is_symlink(de->ext))
+				xlate_len -= 4;
 		if (xlate_len == name_len)
 			if ((!anycase && !memcmp(name, bufname, xlate_len)) ||
 			    (anycase && !fat_strnicmp(nls_io, name, bufname,
@@ -329,6 +340,9 @@
 			xlate_len = utf8
 				?utf8_wcstombs(bufname, unicode, sizeof(bufname))
 				:uni16_to_x8(bufname, unicode, uni_xlate, nls_io);
+			if (showsymlinks && (xlate_len == name_len + 4)
+						&& is_symlink(de->ext))
+				xlate_len -= 4;
 			if (xlate_len != name_len)
 				continue;
 			if ((!anycase && !memcmp(name, bufname, xlate_len)) ||
@@ -367,6 +381,7 @@
 	int isvfat = MSDOS_SB(sb)->options.isvfat;
 	int utf8 = MSDOS_SB(sb)->options.utf8;
 	int nocase = MSDOS_SB(sb)->options.nocase;
+	int showsymlinks = MSDOS_SB(sb)->options.symlinks;
 	unsigned short opt_shortname = MSDOS_SB(sb)->options.shortname;
 	int ino, inum, chi, chl, i, i2, j, last, last_u, dotoffset = 0;
 	loff_t cpos;
@@ -556,6 +571,8 @@
 	}
 
 	if (!long_slots||shortnames) {
+		if (showsymlinks && i > 4 && is_symlink(de->ext))
+			i -= 4;
 		if (both)
 			bufname[i] = '\0';
 		if (filldir(dirent, bufname, i, *furrfu, inum,
@@ -567,6 +584,8 @@
 			? utf8_wcstombs(longname, unicode, sizeof(longname))
 			: uni16_to_x8(longname, unicode, uni_xlate,
 				      nls_io);
+		if (showsymlinks && long_len > 4 && is_symlink(de->ext))
+			long_len -= 4;
 		if (both) {
 			memcpy(&longname[long_len+1], bufname, i);
 			long_len += i;
diff -Nru linux-old/fs/fat/file.c linux-new/fs/fat/file.c
--- linux-old/fs/fat/file.c	Sun Aug 12 13:56:56 2001
+++ linux-new/fs/fat/file.c	Tue Jun 11 17:02:45 2002
@@ -16,6 +16,7 @@
 #include <linux/string.h>
 #include <linux/pagemap.h>
 #include <linux/fat_cvf.h>
+#include <linux/slab.h>
 
 #include <asm/uaccess.h>
 #include <asm/system.h>
@@ -36,6 +37,15 @@
 	setattr:	fat_notify_change,
 };
 
+int fat_readlink(struct dentry *dentry, char *buffer, int buflen);
+int fat_follow_link(struct dentry *dentry, struct nameidata *nd);
+
+struct inode_operations fat_symlink_inode_operations = {
+	readlink:       fat_readlink,
+	follow_link:    fat_follow_link,
+	setattr:	fat_notify_change,
+};
+
 ssize_t fat_file_read(
 	struct file *filp,
 	char *buf,
@@ -134,3 +144,201 @@
 	inode->i_ctime = inode->i_mtime = CURRENT_TIME;
 	mark_inode_dirty(inode);
 }
+
+#define FAT_SYMLINK_SHELL_ITEM_LIST	0x01
+#define FAT_SYMLINK_FILE_LOCATION	0x02
+#define FAT_SYMLINK_RELATIVE		0x08
+#define FAT_SYMLINK_LOCAL		0x01
+#define FAT_SYMLINK_NETWORK		0x02
+#define FAT_SYMLINK_ABSOLUTE		"\\\\localhost\\"
+
+int fat_getlink(struct dentry *dentry, char *buffer, int buflen,
+		char **outbuffer)
+{
+	struct page * page;
+	struct address_space *mapping = dentry->d_inode->i_mapping;
+	char * ptr;
+	int ret = -EIO;
+	int offset = 76;
+	unsigned char flags;
+	*outbuffer = buffer;
+
+	page = read_cache_page(mapping, 0, (filler_t *)mapping->a_ops->readpage,
+		NULL);
+	if (IS_ERR(page))
+		goto sync_fail;
+	wait_on_page(page);
+	if (!Page_Uptodate(page))
+		goto async_fail;
+	ptr = kmap(page);
+
+	if (!(ptr && *ptr == 'L'))	/* FIXME: test cele signatury */
+		goto fail_and_free_page;
+
+	flags = *(ptr + 20);
+
+	if (flags & FAT_SYMLINK_RELATIVE) {
+		int i, len;
+		for (i = 0; i <= 2; i++) {
+			if (flags & (1 << i)) {
+				offset += CF_LE_W(*(__u16 *)(ptr + offset));
+				if (i == 0 || i == 2)
+					offset += 2;
+			}
+		}
+		len = CF_LE_W(*(__u16 *)(ptr + offset));
+		if (!buffer) {
+			*outbuffer = kmalloc(len + 1, GFP_KERNEL);
+			if (!(*outbuffer)) {
+				ret = -ENOMEM;
+				goto fail_and_free_page;
+			}
+			memcpy(*outbuffer, ptr + offset + 2, len);
+		} else {
+			if (len > buflen) {
+				len = buflen;
+			}
+			if (copy_to_user(buffer, ptr + offset + 2, len + 1)) {
+				ret = -EFAULT;
+				goto fail_and_free_page;
+			}
+		}
+		(*outbuffer)[len] = 0;
+		for (i = 0; i < len; i++) {
+			if ((*outbuffer)[i] == '\\')
+				(*outbuffer)[i] = '/';
+		}
+		ret = len;
+	} else if (flags & FAT_SYMLINK_FILE_LOCATION) {
+		unsigned char loc_flags;
+		char * first_part = NULL;
+		char * final_part = NULL;
+		int first_part_len, final_part_len;
+		int insert_slash = 0;
+		int total_len;
+
+		if (flags & FAT_SYMLINK_SHELL_ITEM_LIST) {
+			offset += CF_LE_W(*(__u16 *)(ptr + offset)) + 2;
+		}
+		loc_flags = *(ptr + offset + 8);
+
+		if (loc_flags & FAT_SYMLINK_NETWORK) {
+			int new_offset = offset
+				+ CF_LE_W(*(__u16 *)(ptr + offset + 20));
+			new_offset += CF_LE_W(*(__u16 *)(ptr + new_offset + 8));
+			first_part = ptr + new_offset;
+		} else if (loc_flags & FAT_SYMLINK_LOCAL) {
+			first_part = ptr + offset
+				+ CF_LE_W(*(__u16 *)(ptr + offset + 16));
+		}
+
+		if (!first_part)
+			goto fail_and_free_page;
+
+		first_part_len = strlen(first_part);
+		if (!strnicmp(first_part, FAT_SYMLINK_ABSOLUTE,
+			strlen(FAT_SYMLINK_ABSOLUTE))) {
+			first_part += strlen(FAT_SYMLINK_ABSOLUTE) - 1;
+			first_part_len += 1 - strlen(FAT_SYMLINK_ABSOLUTE);
+		}
+
+		final_part = ptr + offset
+				+ CF_LE_W(*(__u16 *)(ptr + offset + 24));
+		final_part_len = strlen(final_part);
+
+		if (final_part_len && (first_part[first_part_len - 1] != '\\'))
+			insert_slash = 1;
+
+		ret = total_len = first_part_len + insert_slash
+							+ final_part_len;
+		if (!buffer) {
+			int i;
+			*outbuffer = kmalloc(first_part_len + insert_slash
+				+ final_part_len + 1, GFP_KERNEL);
+			if (!(*outbuffer)) {
+				ret = -ENOMEM;
+				goto fail_and_free_page;
+			}
+
+			for (i = 0; i < first_part_len; i++) {
+				if (first_part[i] == '\\')
+					(*outbuffer)[i] = '/';
+				else
+					(*outbuffer)[i] = first_part[i];
+			}
+			if (insert_slash) {
+				(*outbuffer)[i] = '/';
+				i++;
+			}
+			for (i = 0; i < final_part_len; i++) {
+				if (final_part[i] == '\\')
+					(*outbuffer)[first_part_len
+						+ insert_slash + i] = '/';
+				else
+					(*outbuffer)[first_part_len
+					+ insert_slash + i] = final_part[i];
+			}
+			(*outbuffer)[first_part_len + insert_slash + i] = 0;
+		} else {
+			int i;
+			if (total_len > buflen)
+				total_len = buflen;
+			if (copy_to_user(buffer, first_part,
+				min(first_part_len, buflen))) {
+				ret = -EFAULT;
+				goto fail_and_free_page;
+			}
+			if (first_part_len + 1 < buflen) {
+				if (insert_slash
+					&& copy_to_user(buffer
+						+ first_part_len, "/", 1)) {
+					ret = -EFAULT;
+					goto fail_and_free_page;
+				}
+				if (copy_to_user(buffer + first_part_len
+					+ insert_slash, final_part,
+					min(final_part_len, buflen
+						- first_part_len
+						- insert_slash))) {
+					ret = -EFAULT;
+					goto fail_and_free_page;
+				}
+			}
+			for (i = 0; i < min(buflen, first_part_len
+				+ insert_slash + final_part_len); i++) {
+				if (buffer[i] == '\\')
+					buffer[i] = '/';
+			}
+			buffer[total_len] = '\000';
+		}
+	}
+
+fail_and_free_page:
+	kunmap(page);
+async_fail:
+        page_cache_release(page);
+sync_fail:
+        return ret;
+}
+
+int fat_readlink(struct dentry *dentry, char *buffer, int buflen)
+{
+	char * out;
+	if (dentry->d_inode->i_size > PAGE_SIZE)
+		return -ENAMETOOLONG;
+	return fat_getlink(dentry, buffer, buflen, &out);
+}
+
+int fat_follow_link(struct dentry *dentry, struct nameidata *nd)
+{
+	int res;
+	char * buffer;
+	if (dentry->d_inode->i_size > PAGE_SIZE)
+		return -ENAMETOOLONG;
+	fat_getlink(dentry, NULL, 0, &buffer);
+	res = vfs_follow_link(nd, buffer);
+	if (buffer)
+		kfree(buffer);
+	return res;
+}
+
diff -Nru linux-old/fs/fat/inode.c linux-new/fs/fat/inode.c
--- linux-old/fs/fat/inode.c	Tue Jun 11 16:54:42 2002
+++ linux-new/fs/fat/inode.c	Tue Jun 11 17:02:45 2002
@@ -225,6 +225,7 @@
 	opts->nocase = 0;
 	opts->shortname = 0;
 	opts->utf8 = 0;
+	opts->symlinks = 0;
 	opts->iocharset = NULL;
 	*debug = *fat = 0;
 
@@ -856,6 +857,13 @@
 	return 0;
 }
 
+static int is_symlink(char *extension)
+{
+	if (strncmp(extension, "LNK", 3) == 0)
+		return 1;
+	return 0;
+}
+
 static int fat_writepage(struct page *page)
 {
 	return block_write_full_page(page,fat_get_block);
@@ -944,6 +952,12 @@
 		inode->i_size = CF_LE_L(de->size);
 	        inode->i_op = &fat_file_inode_operations;
 	        inode->i_fop = &fat_file_operations;
+		if (MSDOS_SB(sb)->options.symlinks && is_symlink(de->ext)) {
+			inode->i_mode &= ~ S_IFREG;
+			inode->i_mode |= S_IFLNK;
+			inode->i_op = &fat_symlink_inode_operations;
+			inode->i_fop = NULL;
+		}
 		inode->i_mapping->a_ops = &fat_aops;
 		MSDOS_I(inode)->mmu_private = inode->i_size;
 	}
diff -Nru linux-old/fs/vfat/namei.c linux-new/fs/vfat/namei.c
--- linux-old/fs/vfat/namei.c	Tue Jun 11 16:53:57 2002
+++ linux-new/fs/vfat/namei.c	Tue Jun 11 17:02:45 2002
@@ -109,6 +109,7 @@
 	opts->numtail = 1;
 	opts->utf8 = 0;
 	opts->shortname = VFAT_SFN_DISPLAY_LOWER | VFAT_SFN_CREATE_WIN95;
+	opts->symlinks = 1;
 	/* for backward compatible */
 	if (opts->nocase) {
 		opts->nocase = 0;
@@ -155,6 +156,8 @@
 						| VFAT_SFN_CREATE_WIN95;
 			else
 				ret = 0;
+		} else if (!strcmp(this_char,"nosymlinks")) {
+			opts->symlinks = 0;
 		}
 		if (this_char != options)
 			*(this_char-1) = ',';
@@ -213,7 +216,9 @@
 	name = qstr->name;
 	while (len && name[len-1] == '.')
 		len--;
-
+	if (MSDOS_SB(dentry->d_inode->i_sb)->options.symlinks
+		&& len > 4 && strnicmp(name + len - 4, ".lnk", 4) == 0)
+		len -= 4;
 	qstr->hash = full_name_hash(name, len);
 
 	return 0;
@@ -236,6 +241,9 @@
 	name = qstr->name;
 	while (len && name[len-1] == '.')
 		len--;
+	if (MSDOS_SB(dentry->d_inode->i_sb)->options.symlinks
+		&& len > 4 && strnicmp(name + len - 4, ".lnk", 4) == 0)
+		len -= 4;
 
 	hash = init_name_hash();
 	while (len--)
@@ -1248,6 +1256,8 @@
 
 }
 
+static int vfat_symlink ( struct inode *dir, struct dentry *dentry,
+                 const char *symname);
 
 /* Public inode operations for the VFAT fs */
 struct inode_operations vfat_dir_inode_operations = {
@@ -1258,6 +1268,7 @@
 	rmdir:		vfat_rmdir,
 	rename:		vfat_rename,
 	setattr:	fat_notify_change,
+	symlink:	vfat_symlink,
 };
 
 struct super_block *vfat_read_super(struct super_block *sb,void *data,
@@ -1285,3 +1296,139 @@
 
 	return res;
 }
+
+/*
+ * Function vfat_symlink_fill takes a pointer to target of the
+ * new symlink and a pointer to buffer and creates correct content
+ * of the buffer, to be written as a file specifying the symlink.
+ * If however the buffer pointer is NULL, it doesn't write to it.
+ * In any case it returns the length of the new file, so this function
+ * should be called twice -- first with NULL as buffer and after
+ * allocating the exact memory, with the pointer to that buffer.
+ *
+ * The relative symlink is stored as relative symlink with the description
+ * the same as the symlink, the absolute symlink is stored as \\localhost\
+ * network symlink with description matching the symlink path.
+ */
+
+#define FAT_SYMLINK_FILE_START	"L\000\000\000\001\024\002\000\000\000\000\000\xc0\000\000\000\000\000\000\x46"
+#define FAT_SYMLINK_LOCALHOST	"\\\\localhost\\"
+
+static int vfat_symlink_fill(const char * symname, char * buffer)
+{
+	int res = 0;
+	int symnamelen = strlen(symname);
+
+	if (buffer) {
+		memcpy(buffer, FAT_SYMLINK_FILE_START, 20);
+		memset(buffer + 20, 0, 76 - 20);
+		*(__u32*)(buffer + 60) = CT_LE_L(1);
+	}
+
+	if (*symname == '/') {
+		res = 76 + symnamelen + 12 + 1 + 20 + 28 + 1 + symnamelen + 2;
+		if (buffer) {
+			int i;
+			*(__u32*)(buffer + 20) = CT_LE_L(6);
+			*(__u32*)(buffer + 76) = CT_LE_L(res - 76);
+			*(__u32*)(buffer + 80) = CT_LE_L(28);
+			*(__u32*)(buffer + 84) = CT_LE_L(2);
+			*(__u32*)(buffer + 96) = CT_LE_L(28);
+			*(__u32*)(buffer + 100) = CT_LE_L(28 + 20 + 13);
+			*(__u32*)(buffer + 104) = CT_LE_L(symnamelen + 12 + 1 + 20);
+			*(__u32*)(buffer + 112) = CT_LE_L(20);
+			memcpy(buffer + 124, FAT_SYMLINK_LOCALHOST, 13);
+			for (i = 1; i <= symnamelen; i++) {
+				if (*(symname + i) == '/')
+					*(buffer + 124 + 12 + i) = '\\';
+				else
+					*(buffer + 124 + 12 + i)
+							= *(symname + i);
+			}
+			*(__u32*)(buffer + 124 + 12 + symnamelen + 1)
+							= CT_LE_W(symnamelen);
+			memcpy(buffer + 124 + 12 + symnamelen + 1 + 2,
+				symname, symnamelen);
+		}
+	} else {
+		res = 76 + 2 + symnamelen + 2 + symnamelen + 1;
+		if (buffer) {
+			int i;
+			*(__u32*)(buffer + 20) = CT_LE_L(12);
+			*(__u16*)(buffer + 76) = CT_LE_W(symnamelen);
+			memcpy(buffer + 78, symname, symnamelen);
+
+			*(__u16*)(buffer + 78 + symnamelen)
+							= CT_LE_W(symnamelen);
+			for (i = 0; i < symnamelen; i++) {
+				if (*(symname + i) == '/')
+					*(buffer + 80 + symnamelen + i) = '\\';
+				else
+					*(buffer + 80 + symnamelen + i)
+							= *(symname + i);
+			}
+		}
+	}
+
+	return res;
+}
+
+/*
+ * Function vfat_symlink creates new symlink file on a vfat partition.
+ * First it adds the .lnk extension which on vfat will denote the symlink
+ * type. To do this, since we're making the name longer, we may need
+ * to allocate new d_name.name. Then we allocate buffer for the content
+ * of the symlink file, let vfat_symlink_fill to fill the buffer, create
+ * the file, release the buffer and are done.
+ */
+ 
+static int vfat_symlink ( struct inode *dir, struct dentry *dentry,
+                 const char *symname)
+{
+	char * buffer;
+        int ret, len;
+
+	int d_name_len = dentry->d_name.len;
+
+	if (d_name_len + 4 + 1 > sizeof(dentry->d_iname)) {
+		char * new_name = kmalloc(d_name_len + 4 + 1, GFP_KERNEL);
+		if (!new_name) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		memcpy(new_name, dentry->d_name.name, d_name_len);
+		if (dentry->d_name.name != dentry->d_iname)
+			kfree(dentry->d_name.name);
+		dentry->d_name.name = new_name;
+	}
+	memcpy((unsigned char *)dentry->d_name.name + d_name_len, ".lnk", 5);
+	dentry->d_name.len += 4;
+
+        ret = vfat_create(dir, dentry, S_IFLNK | 0777);
+        if (ret) {
+                printk(KERN_WARNING "vfat_symlink: create failed (%d)\n", ret);
+                goto out;
+        }
+
+	buffer = kmalloc(vfat_symlink_fill(symname, NULL), GFP_KERNEL);
+	if (!buffer) {
+		ret = -ENOMEM;
+		goto out_unlink;
+	}
+
+	len = vfat_symlink_fill(symname, buffer);
+	ret = block_symlink(dentry->d_inode, buffer, len);
+	kfree(buffer);
+
+	if (ret < 0)
+		goto out_unlink;
+out:
+	return ret;
+
+out_unlink:
+	printk(KERN_WARNING "vfat_symlink: write failed, unlinking\n");
+	vfat_unlink (dir, dentry);
+	d_drop(dentry);
+	goto out;
+}
+
diff -Nru linux-old/include/linux/msdos_fs.h linux-new/include/linux/msdos_fs.h
--- linux-old/include/linux/msdos_fs.h	Fri Oct 12 16:48:42 2001
+++ linux-new/include/linux/msdos_fs.h	Tue Jun 11 17:02:45 2002
@@ -56,7 +56,7 @@
 #define DELETED_FLAG 0xe5 /* marks file as deleted when in name[0] */
 #define IS_FREE(n) (!*(n) || *(const unsigned char *) (n) == DELETED_FLAG)
 
-#define MSDOS_VALID_MODE (S_IFREG | S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO)
+#define MSDOS_VALID_MODE (S_IFREG | S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO | S_IFLNK )
 	/* valid file mode bits */
 
 #define MSDOS_SB(s) (&((s)->u.msdos_sb))
@@ -271,6 +271,7 @@
 /* fat/file.c */
 extern struct file_operations fat_file_operations;
 extern struct inode_operations fat_file_inode_operations;
+extern struct inode_operations fat_symlink_inode_operations;
 extern ssize_t fat_file_read(struct file *filp, char *buf, size_t count,
 			     loff_t *ppos);
 extern int fat_get_block(struct inode *inode, long iblock,
@@ -278,6 +279,11 @@
 extern ssize_t fat_file_write(struct file *filp, const char *buf, size_t count,
 			      loff_t *ppos);
 extern void fat_truncate(struct inode *inode);
+/*
+extern int fat_readlink(struct dentry *dentry, char *buffer, int buflen);
+extern int fat_follow_link(struct dentry *, struct nameidata *);
+*/
+
 
 /* fat/inode.c */
 extern void fat_hash_init(void);
diff -Nru linux-old/include/linux/msdos_fs_sb.h linux-new/include/linux/msdos_fs_sb.h
--- linux-old/include/linux/msdos_fs_sb.h	Fri Oct 12 16:48:42 2001
+++ linux-new/include/linux/msdos_fs_sb.h	Tue Jun 11 17:02:45 2002
@@ -26,7 +26,8 @@
 		 numtail:1,       /* Does first alias have a numeric '~1' type tail? */
 		 atari:1,         /* Use Atari GEMDOS variation of MS-DOS fs */
 		 fat32:1,	  /* Is this a FAT32 partition? */
-		 nocase:1;	  /* Does this need case conversion? 0=need case conversion*/
+		 nocase:1,	  /* Does this need case conversion? 0=need case conversion*/
+		 symlinks:1;	  /* Do we want to support symlinks? */
 };
 
 struct msdos_sb_info {
