diff --git a/Documentation/filesystems/matt.txt b/Documentation/filesystems/matt.txt
new file mode 100644
index 0000000..5e73880
--- /dev/null
+++ b/Documentation/filesystems/matt.txt
@@ -0,0 +1,258 @@
+Matt McCutchen's Filesystem Enhancements
+========================================
+
+The filesystem support in my custom kernel has a number of enhancements that
+address what I consider to be deficiencies in functionality and security.
+
+- Traversing sticky directories
+- Creating entries in sticky directories
+- Hard-linking others' files
+- Moving others' directories
+- Meaningful symlink permissions
+- lchmod
+- lutimes
+- writelink (not implemented)
+- Userspace support
+
+There is a section below documenting each.  Finally there is a section on
+userspace support.
+
+Traversing sticky directories
+-----------------------------
+Using this kernel, to access a file in someone else's sticky directory, you
+must own the target file or have some permission (r, w, or x) on it.  If you
+try to look up a file that isn't yours and on which you have no permission,
+you get EPERM.  Readdir still returns all directory entries (and on some
+filesystems it gives you the files' i-numbers and types); in a future version
+of the custom kernel, readdir may omit entries you aren't allowed to traverse.
+
+If you have only execute permission on a directory, the nonexistence of files
+in the directory is concealed: if you try to look up a nonexistent file, you
+get EPERM instead of ENOENT.  If you have read permission on the directory,
+you can list it with readdir and see whether a given file name is listed,
+while if you have write permission, you can attempt to create a new file with
+that name and see whether you get EACCES because of an existing, unwritable
+file.  Concealing a file's nonexistence would be pointless in either of these
+cases, so you get ENOENT if you have read and/or write permission in addition
+to execute.
+
+Why is this behavior useful?  If you want to let everyone read a folder in
+your home directory, you can set its permissions to 755.  To get to the
+folder, however, people need execute on your home directory, and giving them
+execute invites lots of abuse.  People can guess filenames and see if those
+files exist in your home directory; if the files exist, people can stat them
+and learn their atimes, mtimes, and sizes.  Maybe they won't hit upon any of
+your personal files, but the names of your mailbox and your dotfiles are
+likely to be well-known.  People can find out when you last got mail and how
+big the mail was (if they've been watching the size of your mailbox), what
+programs have written configuration files recently, and so forth.
+
+If you use my kernel, you can stop the abuse by making your home directory
+sticky (mode 1711).  People can still get to the public folder, but trying to
+access any other name will give them EPERM.  They can't even learn whether
+files of given names exist, let alone stat them.
+
+Alternatively, you can set your home directory to mode 1755, in which case
+others can list the names of all files but only see stat information for the
+public ones.  If ls fails to stat a file, it shows question marks for the
+file's attributes and gives the name a red background.  Maybe you've already
+seen this if you've listed a directory of the silly mode 600.
+
+Here's what another user's home directory might look like:
+        drwxr-xr-t  81 matt matt 4096 Jan 18 16:13 .
+        drwxr-xr-x   3 root root 4096 Jan  8 16:46 ..
+        ?---------   ? ?    ?       ?            ? .bashrc
+        drwxr-xr-x   1 matt matt   18 Jan 18 16:14 public
+        ?---------   ? ?    ?       ?            ? private
+
+If you use qmail, making your home directory sticky will tell qmail to hold
+your email.  I recommend you modify qmail to use the setuid bit instead of the
+sticky bit to hold email since setuid on directories currently does nothing.
+I created such a modified qmail for my computer.
+
+Creating entries in sticky directories
+--------------------------------------
+In the standard kernel, linking or moving someone else's file into someone
+else's sticky directory is legal but irreversible.  My kernel forbids this.
+Just as it doesn't let you delete an entry for someone else's file from
+someone else's sticky directory, it doesn't let you create such an entry.
+
+Hard-linking others' files
+--------------------------
+My school's Linux server hosts a number of Web sites for various school clubs.
+The members of each club belong to a group with permission to write to their
+Web site.  This was set up long before default ACLs were available, so the
+sysadmins needed a way to allow people to write to files added to Web sites by
+other people with restrictive umasks.  So they wrote a cron job that would
+forcibly "chmod -R g+w" on each Web site.  They were lucky that nobody was
+devious enough to hard-link /etc/passwd into a Web site, let its group-write
+bit get turned on, and compromise the system.  One can avoid this scenario by
+restricting hard linking.
+
+This kernel only lets you create a hard link to a file you don't own if you
+"control" the directory entry by which you name the file, meaning that
+stickiness or write permission of the containing directory do not prevent you
+from deleting the entry.  In 99% of cases, this means you can hard link to a
+file if and only if you can move it.  Immutable and append-only attributes on
+the containing directory affect moving the file but not hard-linking it.
+
+The upshot is that the only risk you take by running a recursive permission
+resetter is that a user who controls a directory entry to one of your files
+may be able to cause that file's permissions to change to the new value you
+are applying.  If every file of yours whose directory entry is controlled by
+someone else also grants that person read and write permission, this risk is
+not a problem.  This is almost always the case, but watch out for programs
+dumping core in untrusted directories.
+
+It appears to me that most legitimate purposes for hard links to others' files
+(e.g., saving disk space) are served equally well by symlinks.
+
+Moving others' directories
+--------------------------
+At my school, people taking a certain computer class once copied their
+programs into the teacher's dropbox on the above-mentioned Linux server.  The
+submitted directories got 755 permissions, and the teacher could not delete
+them when he was finished grading them!
+
+In general, if you own a directory, you should be able to delete any file from
+it.  However, you can only delete a directory if you have enough write
+permission on the stuff inside to empty out the directory first.  I considered
+having a system-wide trash area to which people can move offending directories
+and a cron job to clean out the area as root, but a restriction on rename(2)
+ruins this approach: since moving a directory causes its .. entry to change,
+you can only move directories that you can write.
+
+My custom kernel lifts the restriction so that a system-wide trash area can be
+implemented.  If you wish to implement one, keep in mind that you need a trash
+area on each filesystem that is writable by non-root users, and your move-to-
+trash command needs to select the trash area on the same filesystem as the
+file being trashed.
+
+Meaningful symlink permissions
+------------------------------
+This is the most exciting of my enhancements, but it is also potentially the
+most disruptive.  On reiserfs filesystems mounted with the new "symlink-perms"
+mount option, my kernel allows symlinks to have permissions and/or access
+ACLs just like other files.  A newly created symlink gets its permissions
+from the creator's umask or the directory's default ACL, as usual.
+Permissions have the following meanings:
+
+    - readlink requires read permission
+    - writelink (not implemented) will require write permission
+    - traversal requires execute permission
+
+This way, you can let people access some of the symlinks in a directory but
+not others.  Or, if you are using files of secret names in conjunction with
+directories that grant only execute permission, you can give others
+execute-only symlinks that let them access the files but not learn their
+names.  In addition, if you're using a symlink as a convenient miniature text
+file, you can make it non-executable so people don't try to follow it.
+(Unfortunately, this currently can't be done when you create a link.)
+
+Symlink permissions are a nice complement to the enhanced sticky bit.  Using
+earlier versions of the custom kernel, if you opened your home directory to
+others (mode 1711), there was still no way to hide symlinks: they always
+appeared normally in the file listing.  Now you can hide them by giving them
+700 permissions.
+
+My /usr/local/bin has root:wheel ownership, 2775 permissions, and a default
+ACL of 775.  Few installers respect the default ACL, so I occasionally have
+to fix the permissions of installed files.  Now I can scan down the
+permissions column of the directory listing without being distracted by
+"lrwxrwxrwx" entries.
+
+Again, symlink permissions are only enforced, initialized as above, and
+changeable by users on reiserfs filesystems mounted with the option
+"symlink-perms".  On other filesystems, new symlinks get 777 permissions, and
+anyone who can stat a symlink can traverse and readlink it.  (The necessary
+changes to support symlink permissions are split between the VFS layer and
+individual filesystems, and I didn't feel like modifying every single
+filesystem implementation, so I modified only reiserfs because it is my
+favorite.)  (I stopped enforcing symlink permissions on all filesystems when
+it interfered with readlinking /proc/*/fd/* entries for files open for writing
+only.)
+
+If you use RPM, I recommend that you do not enable symlink permissions on your
+root filesystem because they might confuse RPM verification.  On the other
+hand, users would probably like symlink permissions supported on their home
+directories.
+
+lchmod
+------
+This system call changes the permissions on a file without following it if it
+happens to be a symlink.  (Plain chmod will change the permissions on the
+target of a symlink.)  Of course, you must own the file.  If the file is
+indeed a symlink but symlink permissions are disabled on the filesystem, you
+get ENOTSUP.
+
+        #313: int lchmod(const char *linkpath, mode_t mode);
+        #314: int lchmodat(int relative_to_fd,
+                         const char *linkpath, mode_t mode);
+
+By the way, you can change a symlink's access ACL with lsetxattr(2) or
+"setfattr -h"; if ACLs and/or symlink permissions are disabled, you get
+ENOTSUP.
+
+lutimes
+-------
+As lchmod is a counterpart to chmod that does not follow symlinks, lutimes is
+a counterpart to utimes that does not follow symlinks.  You can use it to
+change the atime and mtime of a symlink on any filesystem.  (Originally
+symlink times could only be changed on reiserfs, but this capability doesn't
+seem too dangerous; a filesystem implementation that really can't handle
+changing symlink times should complain in setattr.)  As with utimes, you must
+have write permission to set the atime and mtime to the current time, and you
+must own the file to set the atime and mtime arbitrarily.
+
+        #315: int lutimes(const char *path, const struct timeval times[2]);
+        #316: int lutimesat(int relative_to_fd,
+                          const char *path, const struct timeval times[2]);
+
+writelink (not implemented)
+---------------------------
+My kernel adds two system calls to let you change a symlink's target in-place.
+They are not yet implemented; they follow the path to the link but then give
+ENOSYS.  Their declarations and system-call ID numbers are as follows:
+
+        #311: int writelink(const char *linkpath, const char *new_target);
+        #312: int writelinkat(int relative_to_fd,
+                            const char *linkpath, const char *new_target);
+
+I plan to consult the reiserfs people to learn how to implement changing
+symlink targets in-place.  If this is practical, I will then add writelink
+support for reiserfs, controlled by a mount option "writelink".  You probably
+wouldn't want to enable "writelink" on filesystems that still create their
+symlinks with 777 permissions.
+
+Userspace support
+-----------------
+I have made all the necessary changes to the kernel to support the
+enhancements described here.  Some, which merely tighten security, show up in
+userspace only as additional errors.  Others require corresponding changes to
+userspace tools and libraries to be useful.
+
+For example, to call any of the six new system calls, you must either use
+syscall(2) and provide the system call number given here or use a customized C
+library that knows about the calls.  I have made a customized glibc.
+
+Some command-line utilities could also use enhancement.  Eventually I plan to
+customize coreutils to add support for "chmod -h", "touch -h", "getfacl -h",
+and "setfacl -h" and to make the "+" indicating nontrivial ACLs appear when it
+should on symlinks in "ls -l" output.
+
+In the meantime, I have prepared a small collection of proof-of-concept
+userspace utilities that work but are rather inconvenient to use.  There's
+lchmod:
+        $ lchmod 0700 mylink
+        $ lchmod 0775 mylink
+There's lutimes:
+        $ lutimes myfile 1146518442 0 1146518442 0   # atime{s,ns} mtime{s,ns}
+        $ lutimes myfile                             # both to current time
+And there's even writelink:
+        $ writelink mylink newtarget
+        writelink: Function not implemented
+
+--------------
+Matt McCutchen
+hashproduct@gmail.com
+http://kepreon.com/~matt/
diff --git a/README.matt b/README.matt
new file mode 100644
index 0000000..77ed9dc
--- /dev/null
+++ b/README.matt
@@ -0,0 +1,2 @@
+This is Matt McCutchen's custom kernel.  It has several filesystem-related
+enhancements.  See Documentation/filesystems/matt.txt for details.
diff --git a/arch/i386/kernel/.gitignore b/arch/i386/kernel/.gitignore
index 40836ad..e8ef014 100644
--- a/arch/i386/kernel/.gitignore
+++ b/arch/i386/kernel/.gitignore
@@ -1 +1,3 @@
+vsyscall-int80.so
+vsyscall-sysenter.so
 vsyscall.lds
diff --git a/arch/i386/kernel/syscall_table.S b/arch/i386/kernel/syscall_table.S
index ac687d0..cb2232a 100644
--- a/arch/i386/kernel/syscall_table.S
+++ b/arch/i386/kernel/syscall_table.S
@@ -310,3 +310,9 @@ ENTRY(sys_call_table)
 	.long sys_pselect6
 	.long sys_ppoll
 	.long sys_unshare		/* 310 */
+	.long sys_writelink
+	.long sys_writelinkat
+	.long sys_lchmod
+	.long sys_lchmodat
+	.long sys_lutimes		/* 315 */
+	.long sys_lutimesat
diff --git a/fs/namei.c b/fs/namei.c
index 8dc2b03..21157cf 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -209,16 +209,19 @@ int generic_permission(struct inode *ino
 	/*
 	 * Read/write DACs are always overridable.
 	 * Executable DACs are overridable if at least one exec bit is set.
+	 * MATT: Execute can be overridden on symlinks as well as directories.
 	 */
 	if (!(mask & MAY_EXEC) ||
-	    (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode))
+	    (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))
 		if (capable(CAP_DAC_OVERRIDE))
 			return 0;
 
 	/*
-	 * Searching includes executable on directories, else just read.
+	 * Searching includes executable on directories (MATT: and symlinks),
+	 * else just read.
 	 */
-	if (mask == MAY_READ || (S_ISDIR(inode->i_mode) && !(mask & MAY_WRITE)))
+	if (mask == MAY_READ || ((S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))
+		&& !(mask & MAY_WRITE)))
 		if (capable(CAP_DAC_READ_SEARCH))
 			return 0;
 
@@ -599,12 +602,21 @@ static inline void path_to_nameidata(str
 static inline int do_follow_link(struct path *path, struct nameidata *nd)
 {
 	int err = -ELOOP;
+	int (*gse)(struct super_block *sb);
 	if (current->link_count >= MAX_NESTED_LINKS)
 		goto loop;
 	if (current->total_link_count >= 40)
 		goto loop;
 	BUG_ON(nd->depth >= MAX_NESTED_LINKS);
 	cond_resched();
+	/* MATT: You need execute permission to follow a symlink on a
+	 * filesystem that uses symlink permissions. */
+	gse = path->dentry->d_inode->i_sb->s_op->supported_extras;
+	if (gse && (gse(path->dentry->d_inode->i_sb) & FS_EXTRA_SYMLINK_PERMS)) {
+		err = permission(path->dentry->d_inode, MAY_EXEC, NULL);
+		if (err)
+			goto loop;
+	}
 	err = security_inode_follow_link(path->dentry, nd);
 	if (err)
 		goto loop;
@@ -769,6 +781,66 @@ fail:
 }
 
 /*
+ * It's inline, so penalty for filesystems that don't use sticky bit is
+ * minimal.
+ * MATT -- null inode allowed, it's as if nobody owns it
+ */
+static inline int check_sticky(struct inode *dir, struct inode *inode)
+{
+	if (!(dir->i_mode & S_ISVTX))
+		return 0;
+	if (inode && inode->i_uid == current->fsuid)
+		return 0;
+	if (dir->i_uid == current->fsuid)
+		return 0;
+	return !capable(CAP_FOWNER);
+}
+
+/*
+ * MATT -- Will a new entry refuse to adhere to the directory?  :)
+ */
+static inline int check_unsticky(struct inode *dir, uid_t new_owner)
+{
+	if (!(dir->i_mode & S_ISVTX))
+		return 0;
+	if (new_owner == current->fsuid)
+		return 0;
+	if (dir->i_uid == current->fsuid)
+		return 0;
+	return !capable(CAP_FOWNER);
+}
+
+/*
+ * MATT
+ * Check whether we can traverse a directory entry; return 0 or -Exxxx.
+ * 1. We can if the directory entry isn't sticky for us (see check_sticky).
+ * 2. If the entry points nowhere, we can traverse it if we can read or
+ *    write the directory.
+ * 3. If the entry points to a file, we can traverse it if we can read,
+ *    write, or execute the file.
+ * Because of the clumsy interface, we need several permission checks.
+ */
+static inline int may_traverse(struct inode *dir,struct inode *inode)
+{
+	if (!check_sticky(dir, inode))
+		return 0;
+	if (inode) {
+		if (permission(inode, MAY_READ, NULL) == 0
+			|| permission(inode, MAY_WRITE, NULL) == 0
+			|| permission(inode, MAY_EXEC, NULL) == 0)
+			return 0;
+		else
+			return -EPERM;
+	} else {
+		if (permission(dir, MAY_READ, NULL) == 0
+			|| permission(dir, MAY_WRITE, NULL) == 0)
+			return 0;
+		else
+			return -EPERM;
+	}
+}
+
+/*
  * Name resolution.
  * This is the basic name resolution function, turning a pathname into
  * the final dentry. We expect 'base' to be positive and a directory.
@@ -797,6 +869,7 @@ static fastcall int __link_path_walk(con
 		unsigned long hash;
 		struct qstr this;
 		unsigned int c;
+		struct inode *dirsave;
 
 		nd->flags |= LOOKUP_CONTINUE;
 		err = exec_permission_lite(inode, nd);
@@ -804,6 +877,7 @@ static fastcall int __link_path_walk(con
 			err = vfs_permission(nd, MAY_EXEC);
  		if (err)
 			break;
+		dirsave = inode;
 
 		this.name = name;
 		c = *(const unsigned char *)name;
@@ -855,8 +929,16 @@ static fastcall int __link_path_walk(con
 		if (err)
 			break;
 
-		err = -ENOENT;
 		inode = next.dentry->d_inode;
+		/*
+		 * MATT -- May we traverse the entry?
+		 * inode might be null; may_traverse checks this case and
+		 * might cause EPERM to conceal whether the inode exists.
+		 */
+		err = may_traverse(dirsave, inode);
+		if (err)
+			goto out_dput;
+		err = -ENOENT;
 		if (!inode)
 			goto out_dput;
 		err = -ENOTDIR; 
@@ -910,6 +992,14 @@ last_component:
 		if (err)
 			break;
 		inode = next.dentry->d_inode;
+		/*
+		 * MATT -- May we traverse the entry?
+		 * inode might be null; may_traverse checks this case and
+		 * might cause EPERM to conceal whether the inode exists.
+		 */
+		err = may_traverse(dirsave, inode);
+		if (err)
+			break;
 		if ((lookup_flags & LOOKUP_FOLLOW)
 		    && inode && inode->i_op && inode->i_op->follow_link) {
 			err = do_follow_link(&next, nd);
@@ -1312,21 +1402,6 @@ int fastcall __user_walk(const char __us
 }
 
 /*
- * It's inline, so penalty for filesystems that don't use sticky bit is
- * minimal.
- */
-static inline int check_sticky(struct inode *dir, struct inode *inode)
-{
-	if (!(dir->i_mode & S_ISVTX))
-		return 0;
-	if (inode->i_uid == current->fsuid)
-		return 0;
-	if (dir->i_uid == current->fsuid)
-		return 0;
-	return !capable(CAP_FOWNER);
-}
-
-/*
  *	Check whether we can remove a link victim from directory dir, check
  *  whether the type of victim is right.
  *  1. We can't do it if dir is read-only (done in permission())
@@ -1385,12 +1460,27 @@ static int may_delete(struct inode *dir,
  *  4. We can't do it if dir is immutable (done in permission())
  */
 static inline int may_create(struct inode *dir, struct dentry *child,
-			     struct nameidata *nd)
+			     struct nameidata *nd, uid_t new_owner)
 {
 	if (child->d_inode)
 		return -EEXIST;
 	if (IS_DEADDIR(dir))
 		return -ENOENT;
+	if (check_unsticky(dir, new_owner))
+		return -EPERM;
+	return permission(dir,MAY_WRITE | MAY_EXEC, nd);
+}
+
+/* Same as may_create but allow the child to already exist.
+ * Separately you should check may_delete on an existing child, if any.
+ */
+static inline int may_create_or_overwrite(struct inode *dir, struct dentry *child,
+			     struct nameidata *nd, uid_t new_owner)
+{
+	if (IS_DEADDIR(dir))
+		return -ENOENT;
+	if (check_unsticky(dir, new_owner))
+		return -EPERM;
 	return permission(dir,MAY_WRITE | MAY_EXEC, nd);
 }
 
@@ -1457,7 +1547,7 @@ void unlock_rename(struct dentry *p1, st
 int vfs_create(struct inode *dir, struct dentry *dentry, int mode,
 		struct nameidata *nd)
 {
-	int error = may_create(dir, dentry, nd);
+	int error = may_create(dir, dentry, nd, current->fsuid);
 
 	if (error)
 		return error;
@@ -1576,6 +1666,7 @@ int open_namei(int dfd, const char *path
 	struct path path;
 	struct dentry *dir;
 	int count = 0;
+	int (*gse)(struct super_block *sb);
 
 	acc_mode = ACC_MODE(flag);
 
@@ -1697,6 +1788,14 @@ do_link:
 	 * are done. Procfs-like symlinks just set LAST_BIND.
 	 */
 	nd->flags |= LOOKUP_PARENT;
+	/* MATT: You need execute permission to follow a symlink on a
+	 * filesystem that uses symlink permissions. */
+	gse = path.dentry->d_inode->i_sb->s_op->supported_extras;
+	if (gse && (gse(path.dentry->d_inode->i_sb) & FS_EXTRA_SYMLINK_PERMS)) {
+		error = permission(path.dentry->d_inode, MAY_EXEC, NULL);
+		if (error)
+			goto exit_dput;
+	}
 	error = security_inode_follow_link(path.dentry, nd);
 	if (error)
 		goto exit_dput;
@@ -1775,7 +1874,7 @@ EXPORT_SYMBOL_GPL(lookup_create);
 
 int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
 {
-	int error = may_create(dir, dentry, NULL);
+	int error = may_create(dir, dentry, NULL, current->fsuid);
 
 	if (error)
 		return error;
@@ -1854,7 +1953,7 @@ asmlinkage long sys_mknod(const char __u
 
 int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 {
-	int error = may_create(dir, dentry, NULL);
+	int error = may_create(dir, dentry, NULL, current->fsuid);
 
 	if (error)
 		return error;
@@ -2118,7 +2217,7 @@ asmlinkage long sys_unlink(const char __
 
 int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname, int mode)
 {
-	int error = may_create(dir, dentry, NULL);
+	int error = may_create(dir, dentry, NULL, current->fsuid);
 
 	if (error)
 		return error;
@@ -2184,7 +2283,7 @@ int vfs_link(struct dentry *old_dentry, 
 	if (!inode)
 		return -ENOENT;
 
-	error = may_create(dir, new_dentry, NULL);
+	error = may_create(dir, new_dentry, NULL, inode->i_uid);
 	if (error)
 		return error;
 
@@ -2192,6 +2291,19 @@ int vfs_link(struct dentry *old_dentry, 
 		return -EXDEV;
 
 	/*
+	 * MATT: New restriction:
+	 * If you don't own the file, you must control its directory entry.
+	 * This cuts down on abuses like hard linking .bashrc under rsync's nose.
+	 */
+	if (inode->i_uid != current->fsuid) {
+		error = permission(dir, MAY_WRITE | MAY_EXEC, NULL);
+		if (error)
+			return -EPERM;
+		if (check_sticky(dir, inode))
+			return -EPERM;
+	}
+	
+	/*
 	 * A link to an append-only or immutable file cannot be created.
 	 */
 	if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
@@ -2311,12 +2423,17 @@ static int vfs_rename_dir(struct inode *
 	/*
 	 * If we are going to change the parent - check write permissions,
 	 * we'll need to flip '..'.
+	 *
+	 * MATT - A user should be able to get rid of any file in a directory
+	 * he owns, but this currently is not possible if the file is someone
+	 * else's nonempty directory.  Lift this restriction so the user can
+	 * move the offending directory into a system-wide trash area.
 	 */
-	if (new_dir != old_dir) {
+	/*if (new_dir != old_dir) {
 		error = permission(old_dentry->d_inode, MAY_WRITE, NULL);
 		if (error)
 			return error;
-	}
+	}*/
 
 	error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry);
 	if (error)
@@ -2387,12 +2504,14 @@ int vfs_rename(struct inode *old_dir, st
 	if (error)
 		return error;
 
-	if (!new_dentry->d_inode)
-		error = may_create(new_dir, new_dentry, NULL);
-	else
-		error = may_delete(new_dir, new_dentry, is_dir);
+	error = may_create_or_overwrite(new_dir, new_dentry, NULL, old_dentry->d_inode->i_uid);
 	if (error)
 		return error;
+	if (new_dentry->d_inode) {
+		error = may_delete(new_dir, new_dentry, is_dir);
+		if (error)
+			return error;
+	}
 
 	if (!old_dir->i_op || !old_dir->i_op->rename)
 		return -EPERM;
diff --git a/fs/open.c b/fs/open.c
index 70e0230..b8b0ac2 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -415,14 +415,14 @@ #endif
  * must be owner or have write permission.
  * Else, update from *times, must be owner or super user.
  */
-long do_utimes(int dfd, char __user *filename, struct timeval *times)
+long do_utimes_1(int dfd, char __user *filename, struct timeval *times, unsigned walk_flags)
 {
 	int error;
 	struct nameidata nd;
 	struct inode * inode;
 	struct iattr newattrs;
 
-	error = __user_walk_fd(dfd, filename, LOOKUP_FOLLOW, &nd);
+	error = __user_walk_fd(dfd, filename, walk_flags, &nd);
 
 	if (error)
 		goto out;
@@ -453,6 +453,7 @@ long do_utimes(int dfd, char __user *fil
 		    (error = vfs_permission(&nd, MAY_WRITE)) != 0)
 			goto dput_and_out;
 	}
+
 	mutex_lock(&inode->i_mutex);
 	error = notify_change(nd.dentry, &newattrs);
 	mutex_unlock(&inode->i_mutex);
@@ -462,6 +463,11 @@ out:
 	return error;
 }
 
+long do_utimes(int dfd, char __user *filename, struct timeval *times)
+{
+	return do_utimes_1(dfd, filename, times, LOOKUP_FOLLOW);
+}
+
 asmlinkage long sys_futimesat(int dfd, char __user *filename, struct timeval __user *utimes)
 {
 	struct timeval times[2];
@@ -476,6 +482,20 @@ asmlinkage long sys_utimes(char __user *
 	return sys_futimesat(AT_FDCWD, filename, utimes);
 }
 
+asmlinkage long sys_lutimesat(int dfd, char __user *filename, struct timeval __user *utimes)
+{
+	struct timeval times[2];
+
+	if (utimes && copy_from_user(&times, utimes, sizeof(times)))
+		return -EFAULT;
+	return do_utimes_1(dfd, filename, utimes ? times : NULL, 0);
+}
+
+asmlinkage long sys_lutimes(char __user *filename, struct timeval __user *utimes)
+{
+	return sys_lutimesat(AT_FDCWD, filename, utimes);
+}
+
 
 /*
  * access() needs to use the real uid/gid, not the effective uid/gid.
@@ -646,15 +666,15 @@ out:
 	return err;
 }
 
-asmlinkage long sys_fchmodat(int dfd, const char __user *filename,
-			     mode_t mode)
+long sys_fchmodat_1(int dfd, const char __user *filename,
+			     mode_t mode, unsigned walk_flags)
 {
 	struct nameidata nd;
 	struct inode * inode;
 	int error;
 	struct iattr newattrs;
 
-	error = __user_walk_fd(dfd, filename, LOOKUP_FOLLOW, &nd);
+	error = __user_walk_fd(dfd, filename, walk_flags, &nd);
 	if (error)
 		goto out;
 	inode = nd.dentry->d_inode;
@@ -667,6 +687,15 @@ asmlinkage long sys_fchmodat(int dfd, co
 	if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
 		goto dput_and_out;
 
+	/* MATT: Only change symlink permissions on filesystems prepared for it. */
+	if (S_ISLNK(inode->i_mode)) {
+		int (*gse)(struct super_block *sb) =
+			inode->i_sb->s_op->supported_extras;
+		error = -EOPNOTSUPP;
+		if (!gse || !(gse(inode->i_sb) & FS_EXTRA_SYMLINK_PERMS))
+			goto dput_and_out;
+	}
+
 	mutex_lock(&inode->i_mutex);
 	if (mode == (mode_t) -1)
 		mode = inode->i_mode;
@@ -681,11 +710,28 @@ out:
 	return error;
 }
 
+asmlinkage long sys_fchmodat(int dfd, const char __user *filename,
+			     mode_t mode)
+{
+	return sys_fchmodat_1(dfd, filename, mode, LOOKUP_FOLLOW);
+}
+
 asmlinkage long sys_chmod(const char __user *filename, mode_t mode)
 {
 	return sys_fchmodat(AT_FDCWD, filename, mode);
 }
 
+asmlinkage long sys_lchmodat(int dfd, const char __user *filename,
+			     mode_t mode)
+{
+	return sys_fchmodat_1(dfd, filename, mode, 0);
+}
+
+asmlinkage long sys_lchmod(const char __user *filename, mode_t mode)
+{
+	return sys_lchmodat(AT_FDCWD, filename, mode);
+}
+
 static int chown_common(struct dentry * dentry, uid_t user, gid_t group)
 {
 	struct inode * inode;
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index 284f785..cc6a898 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -1076,6 +1076,10 @@ static int reiserfs_symlink(struct inode
 
 	/* We would inherit the default ACL here, but symlinks don't get ACLs */
 
+	/* MATT: Might have to umask symlink; copied from open_namei */
+	if (!IS_POSIXACL(parent_dir))
+		mode &= ~current->fs->umask;
+
 	retval = journal_begin(&th, parent_dir->i_sb, jbegin_count);
 	if (retval) {
 		drop_new_inode(inode);
diff --git a/fs/reiserfs/super.c b/fs/reiserfs/super.c
index d63da75..47dedbb 100644
--- a/fs/reiserfs/super.c
+++ b/fs/reiserfs/super.c
@@ -584,6 +584,9 @@ static ssize_t reiserfs_quota_read(struc
 				   loff_t);
 #endif
 
+/* MATT */
+static int reiserfs_supported_extras(struct super_block *);
+
 static struct super_operations reiserfs_sops = {
 	.alloc_inode = reiserfs_alloc_inode,
 	.destroy_inode = reiserfs_destroy_inode,
@@ -602,6 +605,7 @@ #ifdef CONFIG_QUOTA
 	.quota_read = reiserfs_quota_read,
 	.quota_write = reiserfs_quota_write,
 #endif
+	.supported_extras = reiserfs_supported_extras, /* MATT */
 };
 
 #ifdef CONFIG_QUOTA
@@ -907,6 +911,9 @@ #endif
 		{"grpjquota",.arg_required =
 		 'g' | (1 << REISERFS_OPT_ALLOWEMPTY),.values = NULL},
 		{"jqfmt",.arg_required = 'f',.values = NULL},
+		/* MATT: Symlink permissions.  Is writelink next? */
+		{"symlink-perms",.setmask = 1 << REISERFS_SYMLINK_PERMS},
+		{"nosymlink-perms",.clrmask = 1 << REISERFS_SYMLINK_PERMS},
 		{NULL,}
 	};
 
@@ -1172,6 +1179,7 @@ #endif
 	safe_mask |= 1 << REISERFS_ERROR_CONTINUE;
 	safe_mask |= 1 << REISERFS_ERROR_PANIC;
 	safe_mask |= 1 << REISERFS_QUOTA;
+	safe_mask |= 1 << REISERFS_SYMLINK_PERMS;
 
 	/* Update the bitmask, taking care to keep
 	 * the bits we're not allowed to change here */
@@ -2248,6 +2256,12 @@ static ssize_t reiserfs_quota_write(stru
 
 #endif
 
+/* MATT: Tell VFS whether we support symlink permissions. */
+static int reiserfs_supported_extras(struct super_block *sb) {
+	return (REISERFS_SB(sb)->s_mount_opt & (1 << REISERFS_SYMLINK_PERMS)
+		? FS_EXTRA_SYMLINK_PERMS : 0);
+}
+
 static struct super_block *get_super_block(struct file_system_type *fs_type,
 					   int flags, const char *dev_name,
 					   void *data)
diff --git a/fs/reiserfs/xattr_acl.c b/fs/reiserfs/xattr_acl.c
index ab8894c..8c12368 100644
--- a/fs/reiserfs/xattr_acl.c
+++ b/fs/reiserfs/xattr_acl.c
@@ -252,7 +252,9 @@ reiserfs_set_acl(struct inode *inode, in
 	int error;
 	struct reiserfs_inode_info *reiserfs_i = REISERFS_I(inode);
 
-	if (S_ISLNK(inode->i_mode))
+	/* MATT: Symlinks can have ACLs if mount options permit. */
+	if (!(REISERFS_SB(inode->i_sb)->s_mount_opt & (1 << REISERFS_SYMLINK_PERMS))
+			&& S_ISLNK(inode->i_mode))
 		return -EOPNOTSUPP;
 
 	switch (type) {
@@ -323,8 +325,9 @@ reiserfs_inherit_default_acl(struct inod
 	struct posix_acl *acl;
 	int err = 0;
 
-	/* ACLs only get applied to files and directories */
-	if (S_ISLNK(inode->i_mode))
+	/* MATT: Symlinks can have ACLs if mount options permit. */
+	if (!(REISERFS_SB(inode->i_sb)->s_mount_opt & (1 << REISERFS_SYMLINK_PERMS))
+			&& S_ISLNK(inode->i_mode))
 		return 0;
 
 	/* ACLs can only be used on "new" objects, so if it's an old object
@@ -420,7 +423,9 @@ int reiserfs_acl_chmod(struct inode *ino
 	struct posix_acl *acl, *clone;
 	int error;
 
-	if (S_ISLNK(inode->i_mode))
+	/* MATT: Symlinks can have ACLs if mount options permit. */
+	if (!(REISERFS_SB(inode->i_sb)->s_mount_opt & (1 << REISERFS_SYMLINK_PERMS))
+			&& S_ISLNK(inode->i_mode))
 		return -EOPNOTSUPP;
 
 	if (get_inode_sd_version(inode) == STAT_DATA_V1 ||
diff --git a/fs/stat.c b/fs/stat.c
index 9948cc1..9b24090 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -310,7 +310,16 @@ asmlinkage long sys_readlinkat(int dfd, 
 
 		error = -EINVAL;
 		if (inode->i_op && inode->i_op->readlink) {
-			error = security_inode_readlink(nd.dentry);
+			/* MATT: You need read permission to readlink on a
+			 * filesystem that uses symlink permissions. */
+			int (*gse)(struct super_block *sb) =
+				inode->i_sb->s_op->supported_extras;
+			if (gse && (gse(inode->i_sb) & FS_EXTRA_SYMLINK_PERMS))
+				error = permission(inode, MAY_READ, NULL);
+			else
+				error = 0;
+			if (!error)
+				error = security_inode_readlink(nd.dentry);
 			if (!error) {
 				touch_atime(nd.mnt, nd.dentry);
 				error = inode->i_op->readlink(nd.dentry, buf, bufsiz);
@@ -327,6 +336,24 @@ asmlinkage long sys_readlink(const char 
 	return sys_readlinkat(AT_FDCWD, path, buf, bufsiz);
 }
 
+asmlinkage long sys_writelinkat(int dfd, const char __user *path, const char __user *buf)
+{
+	struct nameidata nd;
+	int error;
+
+	error = __user_walk_fd(dfd, path, 0, &nd);
+	if (!error) {
+		/* I'll get around to this later... :) */
+		error = -ENOSYS;
+		path_release(&nd);
+	}
+	return error;
+}
+
+asmlinkage long sys_writelink(const char __user *path, const char __user *buf)
+{
+	return sys_writelinkat(AT_FDCWD, path, buf);
+}
 
 /* ---------- LFS-64 ----------- */
 #ifdef __ARCH_WANT_STAT64
diff --git a/include/asm-i386/.gitignore b/include/asm-i386/.gitignore
new file mode 100644
index 0000000..62b0ac8
--- /dev/null
+++ b/include/asm-i386/.gitignore
@@ -0,0 +1 @@
+asm-offsets.h
diff --git a/include/asm-i386/unistd.h b/include/asm-i386/unistd.h
index dc81a55..5be5255 100644
--- a/include/asm-i386/unistd.h
+++ b/include/asm-i386/unistd.h
@@ -316,8 +316,14 @@ #define __NR_faccessat		307
 #define __NR_pselect6		308
 #define __NR_ppoll		309
 #define __NR_unshare		310
+#define __NR_writelink		311
+#define __NR_writelinkat	312
+#define __NR_lchmod		313
+#define __NR_lchmodat		314
+#define __NR_lutimes		315
+#define __NR_lutimesat		316
 
-#define NR_syscalls 311
+#define NR_syscalls 317
 
 /*
  * user-visible error numbers are in the range -1 - -128: see
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 128d008..ccb2740 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1089,8 +1089,14 @@ struct super_operations {
 
 	ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
 	ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
+	
+	/* MATT: What extra features that VFS knows about is the filesystem
+	 * prepared to handle?  Constants below.  Maybe this should be somewhere else. */
+	int (*supported_extras)(struct super_block *);
 };
 
+#define FS_EXTRA_SYMLINK_PERMS	(1 << 0)
+
 /* Inode state bits.  Protected by inode_lock. */
 #define I_DIRTY_SYNC		1 /* Not dirty enough for O_DATASYNC */
 #define I_DIRTY_DATASYNC	2 /* Data-related inode changes pending */
diff --git a/include/linux/reiserfs_fs_sb.h b/include/linux/reiserfs_fs_sb.h
index 31b4c0b..cb5396d 100644
--- a/include/linux/reiserfs_fs_sb.h
+++ b/include/linux/reiserfs_fs_sb.h
@@ -464,6 +464,8 @@ enum reiserfs_mount_options {
 
 	REISERFS_QUOTA,		/* Some quota option specified */
 
+	REISERFS_SYMLINK_PERMS,	/* MATT: Symlinks have permissions */
+
 	REISERFS_TEST1,
 	REISERFS_TEST2,
 	REISERFS_TEST3,
diff --git a/localversion-matt b/localversion-matt
new file mode 100644
index 0000000..8627d1f
--- /dev/null
+++ b/localversion-matt
@@ -0,0 +1 @@
+.matt4
diff --git a/scripts/kconfig/.gitignore b/scripts/kconfig/.gitignore
index e8ad1f6..bbd742c 100644
--- a/scripts/kconfig/.gitignore
+++ b/scripts/kconfig/.gitignore
@@ -6,6 +6,8 @@ lex.*.c
 *.tab.c
 *.tab.h
 zconf.hash.c
+lkc_defs.h
+qconf.moc
 
 #
 # configuration programs
