drm/nouveau: off by one in init_i2c_device_find()
[safe/jmp/linux-2.6] / fs / 9p / vfs_inode.c
index 1fa5f15..4331b3b 100644 (file)
 #include <linux/namei.h>
 #include <linux/idr.h>
 #include <linux/sched.h>
+#include <linux/slab.h>
 #include <net/9p/9p.h>
 #include <net/9p/client.h>
 
 #include "v9fs.h"
 #include "v9fs_vfs.h"
 #include "fid.h"
+#include "cache.h"
 
 static const struct inode_operations v9fs_dir_inode_operations;
-static const struct inode_operations v9fs_dir_inode_operations_ext;
+static const struct inode_operations v9fs_dir_inode_operations_dotu;
+static const struct inode_operations v9fs_dir_inode_operations_dotl;
 static const struct inode_operations v9fs_file_inode_operations;
+static const struct inode_operations v9fs_file_inode_operations_dotl;
 static const struct inode_operations v9fs_symlink_inode_operations;
+static const struct inode_operations v9fs_symlink_inode_operations_dotl;
 
 /**
  * unixmode2p9mode - convert unix mode bits to plan 9
@@ -59,7 +64,7 @@ static int unixmode2p9mode(struct v9fs_session_info *v9ses, int mode)
        res = mode & 0777;
        if (S_ISDIR(mode))
                res |= P9_DMDIR;
-       if (v9fs_extended(v9ses)) {
+       if (v9fs_proto_dotu(v9ses)) {
                if (S_ISLNK(mode))
                        res |= P9_DMSYMLINK;
                if (v9ses->nodev == 0) {
@@ -101,21 +106,21 @@ static int p9mode2unixmode(struct v9fs_session_info *v9ses, int mode)
 
        if ((mode & P9_DMDIR) == P9_DMDIR)
                res |= S_IFDIR;
-       else if ((mode & P9_DMSYMLINK) && (v9fs_extended(v9ses)))
+       else if ((mode & P9_DMSYMLINK) && (v9fs_proto_dotu(v9ses)))
                res |= S_IFLNK;
-       else if ((mode & P9_DMSOCKET) && (v9fs_extended(v9ses))
+       else if ((mode & P9_DMSOCKET) && (v9fs_proto_dotu(v9ses))
                 && (v9ses->nodev == 0))
                res |= S_IFSOCK;
-       else if ((mode & P9_DMNAMEDPIPE) && (v9fs_extended(v9ses))
+       else if ((mode & P9_DMNAMEDPIPE) && (v9fs_proto_dotu(v9ses))
                 && (v9ses->nodev == 0))
                res |= S_IFIFO;
-       else if ((mode & P9_DMDEVICE) && (v9fs_extended(v9ses))
+       else if ((mode & P9_DMDEVICE) && (v9fs_proto_dotu(v9ses))
                 && (v9ses->nodev == 0))
                res |= S_IFBLK;
        else
                res |= S_IFREG;
 
-       if (v9fs_extended(v9ses)) {
+       if (v9fs_proto_dotu(v9ses)) {
                if ((mode & P9_DMSETUID) == P9_DMSETUID)
                        res |= S_ISUID;
 
@@ -171,12 +176,11 @@ int v9fs_uflags2omode(int uflags, int extended)
 
 /**
  * v9fs_blank_wstat - helper function to setup a 9P stat structure
- * @v9ses: 9P session info (for determining extended mode)
  * @wstat: structure to initialize
  *
  */
 
-static void
+void
 v9fs_blank_wstat(struct p9_wstat *wstat)
 {
        wstat->type = ~0;
@@ -198,6 +202,39 @@ v9fs_blank_wstat(struct p9_wstat *wstat)
        wstat->extension = NULL;
 }
 
+#ifdef CONFIG_9P_FSCACHE
+/**
+ * v9fs_alloc_inode - helper function to allocate an inode
+ * This callback is executed before setting up the inode so that we
+ * can associate a vcookie with each inode.
+ *
+ */
+
+struct inode *v9fs_alloc_inode(struct super_block *sb)
+{
+       struct v9fs_cookie *vcookie;
+       vcookie = (struct v9fs_cookie *)kmem_cache_alloc(vcookie_cache,
+                                                        GFP_KERNEL);
+       if (!vcookie)
+               return NULL;
+
+       vcookie->fscache = NULL;
+       vcookie->qid = NULL;
+       spin_lock_init(&vcookie->lock);
+       return &vcookie->inode;
+}
+
+/**
+ * v9fs_destroy_inode - destroy an inode
+ *
+ */
+
+void v9fs_destroy_inode(struct inode *inode)
+{
+       kmem_cache_free(vcookie_cache, v9fs_inode2cookie(inode));
+}
+#endif
+
 /**
  * v9fs_get_inode - helper function to setup an inode
  * @sb: superblock
@@ -207,65 +244,89 @@ v9fs_blank_wstat(struct p9_wstat *wstat)
 
 struct inode *v9fs_get_inode(struct super_block *sb, int mode)
 {
+       int err;
        struct inode *inode;
        struct v9fs_session_info *v9ses = sb->s_fs_info;
 
        P9_DPRINTK(P9_DEBUG_VFS, "super block: %p mode: %o\n", sb, mode);
 
        inode = new_inode(sb);
-       if (inode) {
-               inode->i_mode = mode;
-               inode->i_uid = current_fsuid();
-               inode->i_gid = current_fsgid();
-               inode->i_blocks = 0;
-               inode->i_rdev = 0;
-               inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
-               inode->i_mapping->a_ops = &v9fs_addr_operations;
-
-               switch (mode & S_IFMT) {
-               case S_IFIFO:
-               case S_IFBLK:
-               case S_IFCHR:
-               case S_IFSOCK:
-                       if (!v9fs_extended(v9ses)) {
-                               P9_DPRINTK(P9_DEBUG_ERROR,
-                                     "special files without extended mode\n");
-                               return ERR_PTR(-EINVAL);
-                       }
-                       init_special_inode(inode, inode->i_mode,
-                                          inode->i_rdev);
-                       break;
-               case S_IFREG:
+       if (!inode) {
+               P9_EPRINTK(KERN_WARNING, "Problem allocating inode\n");
+               return ERR_PTR(-ENOMEM);
+       }
+
+       inode_init_owner(inode, NULL, mode);
+       inode->i_blocks = 0;
+       inode->i_rdev = 0;
+       inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+       inode->i_mapping->a_ops = &v9fs_addr_operations;
+
+       switch (mode & S_IFMT) {
+       case S_IFIFO:
+       case S_IFBLK:
+       case S_IFCHR:
+       case S_IFSOCK:
+               if (!v9fs_proto_dotu(v9ses)) {
+                       P9_DPRINTK(P9_DEBUG_ERROR,
+                                  "special files without extended mode\n");
+                       err = -EINVAL;
+                       goto error;
+               }
+               init_special_inode(inode, inode->i_mode, inode->i_rdev);
+               break;
+       case S_IFREG:
+               if (v9fs_proto_dotl(v9ses)) {
+                       inode->i_op = &v9fs_file_inode_operations_dotl;
+                       inode->i_fop = &v9fs_file_operations_dotl;
+               } else {
                        inode->i_op = &v9fs_file_inode_operations;
                        inode->i_fop = &v9fs_file_operations;
-                       break;
-               case S_IFLNK:
-                       if (!v9fs_extended(v9ses)) {
-                               P9_DPRINTK(P9_DEBUG_ERROR,
-                                       "extended modes used w/o 9P2000.u\n");
-                               return ERR_PTR(-EINVAL);
-                       }
+               }
+
+               break;
+
+       case S_IFLNK:
+               if (!v9fs_proto_dotu(v9ses) && !v9fs_proto_dotl(v9ses)) {
+                       P9_DPRINTK(P9_DEBUG_ERROR, "extended modes used with "
+                                               "legacy protocol.\n");
+                       err = -EINVAL;
+                       goto error;
+               }
+
+               if (v9fs_proto_dotl(v9ses))
+                       inode->i_op = &v9fs_symlink_inode_operations_dotl;
+               else
                        inode->i_op = &v9fs_symlink_inode_operations;
-                       break;
-               case S_IFDIR:
-                       inc_nlink(inode);
-                       if (v9fs_extended(v9ses))
-                               inode->i_op = &v9fs_dir_inode_operations_ext;
-                       else
-                               inode->i_op = &v9fs_dir_inode_operations;
+
+               break;
+       case S_IFDIR:
+               inc_nlink(inode);
+               if (v9fs_proto_dotl(v9ses))
+                       inode->i_op = &v9fs_dir_inode_operations_dotl;
+               else if (v9fs_proto_dotu(v9ses))
+                       inode->i_op = &v9fs_dir_inode_operations_dotu;
+               else
+                       inode->i_op = &v9fs_dir_inode_operations;
+
+               if (v9fs_proto_dotl(v9ses))
+                       inode->i_fop = &v9fs_dir_operations_dotl;
+               else
                        inode->i_fop = &v9fs_dir_operations;
-                       break;
-               default:
-                       P9_DPRINTK(P9_DEBUG_ERROR,
-                               "BAD mode 0x%x S_IFMT 0x%x\n",
-                               mode, mode & S_IFMT);
-                       return ERR_PTR(-EINVAL);
-               }
-       } else {
-               P9_EPRINTK(KERN_WARNING, "Problem allocating inode\n");
-               return ERR_PTR(-ENOMEM);
+
+               break;
+       default:
+               P9_DPRINTK(P9_DEBUG_ERROR, "BAD mode 0x%x S_IFMT 0x%x\n",
+                          mode, mode & S_IFMT);
+               err = -EINVAL;
+               goto error;
        }
+
        return inode;
+
+error:
+       iput(inode);
+       return ERR_PTR(err);
 }
 
 /*
@@ -320,6 +381,21 @@ error:
 }
 */
 
+
+/**
+ * v9fs_clear_inode - release an inode
+ * @inode: inode to release
+ *
+ */
+void v9fs_clear_inode(struct inode *inode)
+{
+       filemap_fdatawrite(inode->i_mapping);
+
+#ifdef CONFIG_9P_FSCACHE
+       v9fs_cache_inode_put_cookie(inode);
+#endif
+}
+
 /**
  * v9fs_inode_from_fid - populate an inode by issuing a attribute request
  * @v9ses: session information
@@ -338,30 +414,31 @@ v9fs_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid,
 
        ret = NULL;
        st = p9_client_stat(fid);
-       if (IS_ERR(st)) {
-               err = PTR_ERR(st);
-               st = NULL;
-               goto error;
-       }
+       if (IS_ERR(st))
+               return ERR_CAST(st);
 
        umode = p9mode2unixmode(v9ses, st->mode);
        ret = v9fs_get_inode(sb, umode);
        if (IS_ERR(ret)) {
                err = PTR_ERR(ret);
-               ret = NULL;
                goto error;
        }
 
        v9fs_stat2inode(st, ret, sb);
        ret->i_ino = v9fs_qid2ino(&st->qid);
+
+#ifdef CONFIG_9P_FSCACHE
+       v9fs_vcookie_set_qid(ret, &st->qid);
+       v9fs_cache_inode_get_cookie(ret);
+#endif
+       p9stat_free(st);
        kfree(st);
+
        return ret;
 
 error:
+       p9stat_free(st);
        kfree(st);
-       if (ret)
-               iput(ret);
-
        return ERR_PTR(err);
 }
 
@@ -375,20 +452,22 @@ error:
 
 static int v9fs_remove(struct inode *dir, struct dentry *file, int rmdir)
 {
+       int retval;
        struct inode *file_inode;
-       struct v9fs_session_info *v9ses;
        struct p9_fid *v9fid;
 
        P9_DPRINTK(P9_DEBUG_VFS, "inode: %p dentry: %p rmdir: %d\n", dir, file,
                rmdir);
 
        file_inode = file->d_inode;
-       v9ses = v9fs_inode2v9ses(file_inode);
        v9fid = v9fs_fid_clone(file);
        if (IS_ERR(v9fid))
                return PTR_ERR(v9fid);
 
-       return p9_client_remove(v9fid);
+       retval = p9_client_remove(v9fid);
+       if (!retval)
+               drop_nlink(file_inode);
+       return retval;
 }
 
 static int
@@ -403,9 +482,9 @@ v9fs_open_created(struct inode *inode, struct file *file)
  * @v9ses: session information
  * @dir: directory that dentry is being created in
  * @dentry:  dentry that is being created
+ * @extension: 9p2000.u extension string to support devices, etc.
  * @perm: create permissions
  * @mode: open mode
- * @extension: 9p2000.u extension string to support devices, etc.
  *
  */
 static struct p9_fid *
@@ -423,12 +502,11 @@ v9fs_create(struct v9fs_session_info *v9ses, struct inode *dir,
        ofid = NULL;
        fid = NULL;
        name = (char *) dentry->d_name.name;
-       dfid = v9fs_fid_clone(dentry->d_parent);
+       dfid = v9fs_fid_lookup(dentry->d_parent);
        if (IS_ERR(dfid)) {
                err = PTR_ERR(dfid);
-               P9_DPRINTK(P9_DEBUG_VFS, "fid clone failed %d\n", err);
-               dfid = NULL;
-               goto error;
+               P9_DPRINTK(P9_DEBUG_VFS, "fid lookup failed %d\n", err);
+               return ERR_PTR(err);
        }
 
        /* clone a fid to use for creation */
@@ -436,8 +514,7 @@ v9fs_create(struct v9fs_session_info *v9ses, struct inode *dir,
        if (IS_ERR(ofid)) {
                err = PTR_ERR(ofid);
                P9_DPRINTK(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err);
-               ofid = NULL;
-               goto error;
+               return ERR_PTR(err);
        }
 
        err = p9_client_fcreate(ofid, name, perm, mode, extension);
@@ -447,14 +524,13 @@ v9fs_create(struct v9fs_session_info *v9ses, struct inode *dir,
        }
 
        /* now walk from the parent so we can get unopened fid */
-       fid = p9_client_walk(dfid, 1, &name, 0);
+       fid = p9_client_walk(dfid, 1, &name, 1);
        if (IS_ERR(fid)) {
                err = PTR_ERR(fid);
                P9_DPRINTK(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err);
                fid = NULL;
                goto error;
-       } else
-               dfid = NULL;
+       }
 
        /* instantiate inode and assign the unopened fid to the dentry */
        inode = v9fs_inode_from_fid(v9ses, fid, dir->i_sb);
@@ -477,9 +553,6 @@ v9fs_create(struct v9fs_session_info *v9ses, struct inode *dir,
        return ofid;
 
 error:
-       if (dfid)
-               p9_client_clunk(dfid);
-
        if (ofid)
                p9_client_clunk(ofid);
 
@@ -519,7 +592,8 @@ v9fs_vfs_create(struct inode *dir, struct dentry *dentry, int mode,
                flags = O_RDWR;
 
        fid = v9fs_create(v9ses, dir, dentry, NULL, perm,
-                               v9fs_uflags2omode(flags, v9fs_extended(v9ses)));
+                               v9fs_uflags2omode(flags,
+                                               v9fs_proto_dotu(v9ses)));
        if (IS_ERR(fid)) {
                err = PTR_ERR(fid);
                fid = NULL;
@@ -599,6 +673,9 @@ static struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
        P9_DPRINTK(P9_DEBUG_VFS, "dir: %p dentry: (%s) %p nameidata: %p\n",
                dir, dentry->d_name.name, dentry, nameidata);
 
+       if (dentry->d_name.len > NAME_MAX)
+               return ERR_PTR(-ENAMETOOLONG);
+
        sb = dir->i_sb;
        v9ses = v9fs_inode2v9ses(dir);
        dfid = v9fs_fid_lookup(dentry->d_parent);
@@ -610,8 +687,8 @@ static struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
        if (IS_ERR(fid)) {
                result = PTR_ERR(fid);
                if (result == -ENOENT) {
-                       d_add(dentry, NULL);
-                       return NULL;
+                       inode = NULL;
+                       goto inst_out;
                }
 
                return ERR_PTR(result);
@@ -628,7 +705,8 @@ static struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
        if (result < 0)
                goto error;
 
-       if ((fid->qid.version) && (v9ses->cache))
+inst_out:
+       if (v9ses->cache)
                dentry->d_op = &v9fs_cached_dentry_operations;
        else
                dentry->d_op = &v9fs_dentry_operations;
@@ -707,6 +785,13 @@ v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
                goto clunk_olddir;
        }
 
+       if (v9fs_proto_dotl(v9ses)) {
+               retval = p9_client_rename(oldfid, newdirfid,
+                                       (char *) new_dentry->d_name.name);
+               if (retval != -ENOSYS)
+                       goto clunk_newdir;
+       }
+
        /* 9P can only handle file rename in the same directory */
        if (memcmp(&olddirfid->qid, &newdirfid->qid, sizeof(newdirfid->qid))) {
                P9_DPRINTK(P9_DEBUG_ERROR,
@@ -750,7 +835,7 @@ v9fs_vfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
        P9_DPRINTK(P9_DEBUG_VFS, "dentry: %p\n", dentry);
        err = -EPERM;
        v9ses = v9fs_inode2v9ses(dentry->d_inode);
-       if (v9ses->cache == CACHE_LOOSE)
+       if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE)
                return simple_getattr(mnt, dentry, stat);
 
        fid = v9fs_fid_lookup(dentry);
@@ -802,7 +887,7 @@ static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr)
        if (iattr->ia_valid & ATTR_SIZE)
                wstat.length = iattr->ia_size;
 
-       if (v9fs_extended(v9ses)) {
+       if (v9fs_proto_dotu(v9ses)) {
                if (iattr->ia_valid & ATTR_UID)
                        wstat.n_uid = iattr->ia_uid;
 
@@ -830,6 +915,8 @@ v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode,
        struct super_block *sb)
 {
        char ext[32];
+       char tag_name[14];
+       unsigned int i_nlink;
        struct v9fs_session_info *v9ses = sb->s_fs_info;
 
        inode->i_nlink = 1;
@@ -841,11 +928,26 @@ v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode,
        inode->i_uid = v9ses->dfltuid;
        inode->i_gid = v9ses->dfltgid;
 
-       if (v9fs_extended(v9ses)) {
+       if (v9fs_proto_dotu(v9ses)) {
                inode->i_uid = stat->n_uid;
                inode->i_gid = stat->n_gid;
        }
-
+       if ((S_ISREG(inode->i_mode)) || (S_ISDIR(inode->i_mode))) {
+               if (v9fs_proto_dotu(v9ses) && (stat->extension[0] != '\0')) {
+                       /*
+                        * Hadlink support got added later to
+                        * to the .u extension. So there can be
+                        * server out there that doesn't support
+                        * this even with .u extension. So check
+                        * for non NULL stat->extension
+                        */
+                       strncpy(ext, stat->extension, sizeof(ext));
+                       /* HARDLINKCOUNT %u */
+                       sscanf(ext, "%13s %u", tag_name, &i_nlink);
+                       if (!strncmp(tag_name, "HARDLINKCOUNT", 13))
+                               inode->i_nlink = i_nlink;
+               }
+       }
        inode->i_mode = p9mode2unixmode(v9ses, stat->mode);
        if ((S_ISBLK(inode->i_mode)) || (S_ISCHR(inode->i_mode))) {
                char type = 0;
@@ -871,10 +973,10 @@ v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode,
        } else
                inode->i_rdev = 0;
 
-       inode->i_size = stat->length;
+       i_size_write(inode, stat->length);
 
        /* not real number of blocks, but 512 byte ones ... */
-       inode->i_blocks = (inode->i_size + 512 - 1) >> 9;
+       inode->i_blocks = (i_size_read(inode) + 512 - 1) >> 9;
 }
 
 /**
@@ -920,7 +1022,7 @@ static int v9fs_readlink(struct dentry *dentry, char *buffer, int buflen)
        if (IS_ERR(fid))
                return PTR_ERR(fid);
 
-       if (!v9fs_extended(v9ses))
+       if (!v9fs_proto_dotu(v9ses))
                return -EBADF;
 
        st = p9_client_stat(fid);
@@ -938,52 +1040,13 @@ static int v9fs_readlink(struct dentry *dentry, char *buffer, int buflen)
        P9_DPRINTK(P9_DEBUG_VFS,
                "%s -> %s (%s)\n", dentry->d_name.name, st->extension, buffer);
 
-       retval = buflen;
-
+       retval = strnlen(buffer, buflen);
 done:
        kfree(st);
        return retval;
 }
 
 /**
- * v9fs_vfs_readlink - read a symlink's location
- * @dentry: dentry for symlink
- * @buffer: buffer to load symlink location into
- * @buflen: length of buffer
- *
- */
-
-static int v9fs_vfs_readlink(struct dentry *dentry, char __user * buffer,
-                            int buflen)
-{
-       int retval;
-       int ret;
-       char *link = __getname();
-
-       if (unlikely(!link))
-               return -ENOMEM;
-
-       if (buflen > PATH_MAX)
-               buflen = PATH_MAX;
-
-       P9_DPRINTK(P9_DEBUG_VFS, " dentry: %s (%p)\n", dentry->d_name.name,
-                                                                       dentry);
-
-       retval = v9fs_readlink(dentry, link, buflen);
-
-       if (retval > 0) {
-               if ((ret = copy_to_user(buffer, link, retval)) != 0) {
-                       P9_DPRINTK(P9_DEBUG_ERROR,
-                                       "problem copying to user: %d\n", ret);
-                       retval = ret;
-               }
-       }
-
-       __putname(link);
-       return retval;
-}
-
-/**
  * v9fs_vfs_follow_link - follow a symlink path
  * @dentry: dentry for symlink
  * @nd: nameidata
@@ -1006,7 +1069,7 @@ static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd)
                        __putname(link);
                        link = ERR_PTR(len);
                } else
-                       link[len] = 0;
+                       link[min(len, PATH_MAX-1)] = 0;
        }
        nd_set_link(nd, link);
 
@@ -1049,7 +1112,7 @@ static int v9fs_vfs_mkspecial(struct inode *dir, struct dentry *dentry,
        struct p9_fid *fid;
 
        v9ses = v9fs_inode2v9ses(dir);
-       if (!v9fs_extended(v9ses)) {
+       if (!v9fs_proto_dotu(v9ses)) {
                P9_DPRINTK(P9_DEBUG_ERROR, "not extended\n");
                return -EPERM;
        }
@@ -1154,6 +1217,8 @@ v9fs_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev)
                sprintf(name, "c %u %u", MAJOR(rdev), MINOR(rdev));
        else if (S_ISFIFO(mode))
                *name = 0;
+       else if (S_ISSOCK(mode))
+               *name = 0;
        else {
                __putname(name);
                return -EINVAL;
@@ -1165,7 +1230,21 @@ v9fs_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev)
        return retval;
 }
 
-static const struct inode_operations v9fs_dir_inode_operations_ext = {
+static const struct inode_operations v9fs_dir_inode_operations_dotu = {
+       .create = v9fs_vfs_create,
+       .lookup = v9fs_vfs_lookup,
+       .symlink = v9fs_vfs_symlink,
+       .link = v9fs_vfs_link,
+       .unlink = v9fs_vfs_unlink,
+       .mkdir = v9fs_vfs_mkdir,
+       .rmdir = v9fs_vfs_rmdir,
+       .mknod = v9fs_vfs_mknod,
+       .rename = v9fs_vfs_rename,
+       .getattr = v9fs_vfs_getattr,
+       .setattr = v9fs_vfs_setattr,
+};
+
+static const struct inode_operations v9fs_dir_inode_operations_dotl = {
        .create = v9fs_vfs_create,
        .lookup = v9fs_vfs_lookup,
        .symlink = v9fs_vfs_symlink,
@@ -1175,7 +1254,6 @@ static const struct inode_operations v9fs_dir_inode_operations_ext = {
        .rmdir = v9fs_vfs_rmdir,
        .mknod = v9fs_vfs_mknod,
        .rename = v9fs_vfs_rename,
-       .readlink = v9fs_vfs_readlink,
        .getattr = v9fs_vfs_getattr,
        .setattr = v9fs_vfs_setattr,
 };
@@ -1197,8 +1275,21 @@ static const struct inode_operations v9fs_file_inode_operations = {
        .setattr = v9fs_vfs_setattr,
 };
 
+static const struct inode_operations v9fs_file_inode_operations_dotl = {
+       .getattr = v9fs_vfs_getattr,
+       .setattr = v9fs_vfs_setattr,
+};
+
 static const struct inode_operations v9fs_symlink_inode_operations = {
-       .readlink = v9fs_vfs_readlink,
+       .readlink = generic_readlink,
+       .follow_link = v9fs_vfs_follow_link,
+       .put_link = v9fs_vfs_put_link,
+       .getattr = v9fs_vfs_getattr,
+       .setattr = v9fs_vfs_setattr,
+};
+
+static const struct inode_operations v9fs_symlink_inode_operations_dotl = {
+       .readlink = generic_readlink,
        .follow_link = v9fs_vfs_follow_link,
        .put_link = v9fs_vfs_put_link,
        .getattr = v9fs_vfs_getattr,