Merge git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty-2.6
[linux-2.6-block.git] / fs / cifs / link.c
index 6e4e8957595d19a124ac1dbab468e407cbf49737..85cdbf831e7baca6c3d3fbdb1ac670e69fc595ad 100644 (file)
@@ -91,6 +91,92 @@ CIFSParseMFSymlink(const u8 *buf,
        return 0;
 }
 
+static int
+CIFSFormatMFSymlink(u8 *buf, unsigned int buf_len, const char *link_str)
+{
+       unsigned int link_len;
+       unsigned int ofs;
+       struct MD5Context md5_ctx;
+       u8 md5_hash[16];
+
+       if (buf_len != CIFS_MF_SYMLINK_FILE_SIZE)
+               return -EINVAL;
+
+       link_len = strlen(link_str);
+
+       if (link_len > CIFS_MF_SYMLINK_LINK_MAXLEN)
+               return -ENAMETOOLONG;
+
+       cifs_MD5_init(&md5_ctx);
+       cifs_MD5_update(&md5_ctx, (const u8 *)link_str, link_len);
+       cifs_MD5_final(md5_hash, &md5_ctx);
+
+       snprintf(buf, buf_len,
+                CIFS_MF_SYMLINK_LEN_FORMAT CIFS_MF_SYMLINK_MD5_FORMAT,
+                link_len,
+                CIFS_MF_SYMLINK_MD5_ARGS(md5_hash));
+
+       ofs = CIFS_MF_SYMLINK_LINK_OFFSET;
+       memcpy(buf + ofs, link_str, link_len);
+
+       ofs += link_len;
+       if (ofs < CIFS_MF_SYMLINK_FILE_SIZE) {
+               buf[ofs] = '\n';
+               ofs++;
+       }
+
+       while (ofs < CIFS_MF_SYMLINK_FILE_SIZE) {
+               buf[ofs] = ' ';
+               ofs++;
+       }
+
+       return 0;
+}
+
+static int
+CIFSCreateMFSymLink(const int xid, struct cifsTconInfo *tcon,
+                   const char *fromName, const char *toName,
+                   const struct nls_table *nls_codepage, int remap)
+{
+       int rc;
+       int oplock = 0;
+       __u16 netfid = 0;
+       u8 *buf;
+       unsigned int bytes_written = 0;
+
+       buf = kmalloc(CIFS_MF_SYMLINK_FILE_SIZE, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       rc = CIFSFormatMFSymlink(buf, CIFS_MF_SYMLINK_FILE_SIZE, toName);
+       if (rc != 0) {
+               kfree(buf);
+               return rc;
+       }
+
+       rc = CIFSSMBOpen(xid, tcon, fromName, FILE_CREATE, GENERIC_WRITE,
+                        CREATE_NOT_DIR, &netfid, &oplock, NULL,
+                        nls_codepage, remap);
+       if (rc != 0) {
+               kfree(buf);
+               return rc;
+       }
+
+       rc = CIFSSMBWrite(xid, tcon, netfid,
+                         CIFS_MF_SYMLINK_FILE_SIZE /* length */,
+                         0 /* offset */,
+                         &bytes_written, buf, NULL, 0);
+       CIFSSMBClose(xid, tcon, netfid);
+       kfree(buf);
+       if (rc != 0)
+               return rc;
+
+       if (bytes_written != CIFS_MF_SYMLINK_FILE_SIZE)
+               return -EIO;
+
+       return 0;
+}
+
 static int
 CIFSQueryMFSymLink(const int xid, struct cifsTconInfo *tcon,
                   const unsigned char *searchName, char **symlinkinfo,
@@ -163,7 +249,8 @@ CIFSCheckMFSymlink(struct cifs_fattr *fattr,
        int rc;
        int oplock = 0;
        __u16 netfid = 0;
-       struct cifsTconInfo *pTcon = cifs_sb->tcon;
+       struct tcon_link *tlink;
+       struct cifsTconInfo *pTcon;
        u8 *buf;
        char *pbuf;
        unsigned int bytes_read = 0;
@@ -175,23 +262,30 @@ CIFSCheckMFSymlink(struct cifs_fattr *fattr,
                /* it's not a symlink */
                return 0;
 
+       tlink = cifs_sb_tlink(cifs_sb);
+       if (IS_ERR(tlink))
+               return PTR_ERR(tlink);
+       pTcon = tlink_tcon(tlink);
+
        rc = CIFSSMBOpen(xid, pTcon, path, FILE_OPEN, GENERIC_READ,
                         CREATE_NOT_DIR, &netfid, &oplock, &file_info,
                         cifs_sb->local_nls,
                         cifs_sb->mnt_cifs_flags &
                                CIFS_MOUNT_MAP_SPECIAL_CHR);
        if (rc != 0)
-               return rc;
+               goto out;
 
        if (file_info.EndOfFile != CIFS_MF_SYMLINK_FILE_SIZE) {
                CIFSSMBClose(xid, pTcon, netfid);
                /* it's not a symlink */
-               return 0;
+               goto out;
        }
 
        buf = kmalloc(CIFS_MF_SYMLINK_FILE_SIZE, GFP_KERNEL);
-       if (!buf)
-               return -ENOMEM;
+       if (!buf) {
+               rc = -ENOMEM;
+               goto out;
+       }
        pbuf = buf;
 
        rc = CIFSSMBRead(xid, pTcon, netfid,
@@ -201,23 +295,28 @@ CIFSCheckMFSymlink(struct cifs_fattr *fattr,
        CIFSSMBClose(xid, pTcon, netfid);
        if (rc != 0) {
                kfree(buf);
-               return rc;
+               goto out;
        }
 
        rc = CIFSParseMFSymlink(buf, bytes_read, &link_len, NULL);
        kfree(buf);
-       if (rc == -EINVAL)
+       if (rc == -EINVAL) {
                /* it's not a symlink */
-               return 0;
+               rc = 0;
+               goto out;
+       }
+
        if (rc != 0)
-               return rc;
+               goto out;
 
        /* it is a symlink */
        fattr->cf_eof = link_len;
        fattr->cf_mode &= ~S_IFMT;
        fattr->cf_mode |= S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
        fattr->cf_dtype = DT_LNK;
-       return 0;
+out:
+       cifs_put_tlink(tlink);
+       return rc;
 }
 
 int
@@ -228,17 +327,17 @@ cifs_hardlink(struct dentry *old_file, struct inode *inode,
        int xid;
        char *fromName = NULL;
        char *toName = NULL;
-       struct cifs_sb_info *cifs_sb_target;
+       struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       struct tcon_link *tlink;
        struct cifsTconInfo *pTcon;
        struct cifsInodeInfo *cifsInode;
 
-       xid = GetXid();
-
-       cifs_sb_target = CIFS_SB(inode->i_sb);
-       pTcon = cifs_sb_target->tcon;
+       tlink = cifs_sb_tlink(cifs_sb);
+       if (IS_ERR(tlink))
+               return PTR_ERR(tlink);
+       pTcon = tlink_tcon(tlink);
 
-/* No need to check for cross device links since server will do that
-   BB note DFS case in future though (when we may have to check) */
+       xid = GetXid();
 
        fromName = build_path_from_dentry(old_file);
        toName = build_path_from_dentry(direntry);
@@ -247,16 +346,15 @@ cifs_hardlink(struct dentry *old_file, struct inode *inode,
                goto cifs_hl_exit;
        }
 
-/*     if (cifs_sb_target->tcon->ses->capabilities & CAP_UNIX)*/
        if (pTcon->unix_ext)
                rc = CIFSUnixCreateHardLink(xid, pTcon, fromName, toName,
-                                           cifs_sb_target->local_nls,
-                                           cifs_sb_target->mnt_cifs_flags &
+                                           cifs_sb->local_nls,
+                                           cifs_sb->mnt_cifs_flags &
                                                CIFS_MOUNT_MAP_SPECIAL_CHR);
        else {
                rc = CIFSCreateHardLink(xid, pTcon, fromName, toName,
-                                       cifs_sb_target->local_nls,
-                                       cifs_sb_target->mnt_cifs_flags &
+                                       cifs_sb->local_nls,
+                                       cifs_sb->mnt_cifs_flags &
                                                CIFS_MOUNT_MAP_SPECIAL_CHR);
                if ((rc == -EIO) || (rc == -EINVAL))
                        rc = -EOPNOTSUPP;
@@ -292,6 +390,7 @@ cifs_hl_exit:
        kfree(fromName);
        kfree(toName);
        FreeXid(xid);
+       cifs_put_tlink(tlink);
        return rc;
 }
 
@@ -304,10 +403,19 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
        char *full_path = NULL;
        char *target_path = NULL;
        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
-       struct cifsTconInfo *tcon = cifs_sb->tcon;
+       struct tcon_link *tlink = NULL;
+       struct cifsTconInfo *tcon;
 
        xid = GetXid();
 
+       tlink = cifs_sb_tlink(cifs_sb);
+       if (IS_ERR(tlink)) {
+               rc = PTR_ERR(tlink);
+               tlink = NULL;
+               goto out;
+       }
+       tcon = tlink_tcon(tlink);
+
        /*
         * For now, we just handle symlinks with unix extensions enabled.
         * Eventually we should handle NTFS reparse points, and MacOS
@@ -321,7 +429,8 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
         * but there doesn't seem to be any harm in allowing the client to
         * read them.
         */
-       if (!(tcon->ses->capabilities & CAP_UNIX)) {
+       if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS)
+           && !(tcon->ses->capabilities & CAP_UNIX)) {
                rc = -EACCES;
                goto out;
        }
@@ -332,8 +441,21 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
 
        cFYI(1, "Full path: %s inode = 0x%p", full_path, inode);
 
-       rc = CIFSSMBUnixQuerySymLink(xid, tcon, full_path, &target_path,
-                                    cifs_sb->local_nls);
+       rc = -EACCES;
+       /*
+        * First try Minshall+French Symlinks, if configured
+        * and fallback to UNIX Extensions Symlinks.
+        */
+       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS)
+               rc = CIFSQueryMFSymLink(xid, tcon, full_path, &target_path,
+                                       cifs_sb->local_nls,
+                                       cifs_sb->mnt_cifs_flags &
+                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
+
+       if ((rc != 0) && (tcon->ses->capabilities & CAP_UNIX))
+               rc = CIFSSMBUnixQuerySymLink(xid, tcon, full_path, &target_path,
+                                            cifs_sb->local_nls);
+
        kfree(full_path);
 out:
        if (rc != 0) {
@@ -342,6 +464,8 @@ out:
        }
 
        FreeXid(xid);
+       if (tlink)
+               cifs_put_tlink(tlink);
        nd_set_link(nd, target_path);
        return NULL;
 }
@@ -351,29 +475,37 @@ cifs_symlink(struct inode *inode, struct dentry *direntry, const char *symname)
 {
        int rc = -EOPNOTSUPP;
        int xid;
-       struct cifs_sb_info *cifs_sb;
+       struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       struct tcon_link *tlink;
        struct cifsTconInfo *pTcon;
        char *full_path = NULL;
        struct inode *newinode = NULL;
 
        xid = GetXid();
 
-       cifs_sb = CIFS_SB(inode->i_sb);
-       pTcon = cifs_sb->tcon;
+       tlink = cifs_sb_tlink(cifs_sb);
+       if (IS_ERR(tlink)) {
+               rc = PTR_ERR(tlink);
+               goto symlink_exit;
+       }
+       pTcon = tlink_tcon(tlink);
 
        full_path = build_path_from_dentry(direntry);
-
        if (full_path == NULL) {
                rc = -ENOMEM;
-               FreeXid(xid);
-               return rc;
+               goto symlink_exit;
        }
 
        cFYI(1, "Full path: %s", full_path);
        cFYI(1, "symname is %s", symname);
 
        /* BB what if DFS and this volume is on different share? BB */
-       if (pTcon->unix_ext)
+       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS)
+               rc = CIFSCreateMFSymLink(xid, pTcon, full_path, symname,
+                                        cifs_sb->local_nls,
+                                        cifs_sb->mnt_cifs_flags &
+                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
+       else if (pTcon->unix_ext)
                rc = CIFSUnixCreateSymLink(xid, pTcon, full_path, symname,
                                           cifs_sb->local_nls);
        /* else
@@ -399,8 +531,9 @@ cifs_symlink(struct inode *inode, struct dentry *direntry, const char *symname)
                        d_instantiate(direntry, newinode);
                }
        }
-
+symlink_exit:
        kfree(full_path);
+       cifs_put_tlink(tlink);
        FreeXid(xid);
        return rc;
 }