8b6427a2a8
This is the third respin of the patch posted yesterday to fix the error handling in cifs_follow_symlink. It also includes a fix for a bogus NULL pointer check in CIFSSMBQueryUnixSymLink that Jeff Moyer spotted. It's possible for CIFSSMBQueryUnixSymLink to return without setting target_path to a valid pointer. If that happens then the current value to which we're initializing this pointer could cause an oops when it's kfree'd. This patch is a little more comprehensive than the last patches. It reorganizes cifs_follow_link a bit for (hopefully) better readability. It should also eliminate the uneeded allocation of full_path on servers without unix extensions (assuming they can get to this point anyway, of which I'm not convinced). On a side note, I'm not sure I agree with the logic of enabling this query even when unix extensions are disabled on the client. It seems like that should disable this as well. But, changing that is outside the scope of this fix, so I've left it alone for now. Reported-by: Jeff Moyer <jmoyer@redhat.com> Signed-off-by: Jeff Layton <jlayton@redhat.com> Reviewed-by: Jeff Moyer <jmoyer@redhat.com> Reviewed-by: Christoph Hellwig <hch@inraded.com> Signed-off-by: Steve French <sfrench@us.ibm.com>
220 lines
6 KiB
C
220 lines
6 KiB
C
/*
|
|
* fs/cifs/link.c
|
|
*
|
|
* Copyright (C) International Business Machines Corp., 2002,2008
|
|
* Author(s): Steve French (sfrench@us.ibm.com)
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
|
* the GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
#include <linux/fs.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/namei.h>
|
|
#include "cifsfs.h"
|
|
#include "cifspdu.h"
|
|
#include "cifsglob.h"
|
|
#include "cifsproto.h"
|
|
#include "cifs_debug.h"
|
|
#include "cifs_fs_sb.h"
|
|
|
|
int
|
|
cifs_hardlink(struct dentry *old_file, struct inode *inode,
|
|
struct dentry *direntry)
|
|
{
|
|
int rc = -EACCES;
|
|
int xid;
|
|
char *fromName = NULL;
|
|
char *toName = NULL;
|
|
struct cifs_sb_info *cifs_sb_target;
|
|
struct cifsTconInfo *pTcon;
|
|
struct cifsInodeInfo *cifsInode;
|
|
|
|
xid = GetXid();
|
|
|
|
cifs_sb_target = CIFS_SB(inode->i_sb);
|
|
pTcon = cifs_sb_target->tcon;
|
|
|
|
/* 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) */
|
|
|
|
fromName = build_path_from_dentry(old_file);
|
|
toName = build_path_from_dentry(direntry);
|
|
if ((fromName == NULL) || (toName == NULL)) {
|
|
rc = -ENOMEM;
|
|
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_MOUNT_MAP_SPECIAL_CHR);
|
|
else {
|
|
rc = CIFSCreateHardLink(xid, pTcon, fromName, toName,
|
|
cifs_sb_target->local_nls,
|
|
cifs_sb_target->mnt_cifs_flags &
|
|
CIFS_MOUNT_MAP_SPECIAL_CHR);
|
|
if ((rc == -EIO) || (rc == -EINVAL))
|
|
rc = -EOPNOTSUPP;
|
|
}
|
|
|
|
d_drop(direntry); /* force new lookup from server of target */
|
|
|
|
/* if source file is cached (oplocked) revalidate will not go to server
|
|
until the file is closed or oplock broken so update nlinks locally */
|
|
if (old_file->d_inode) {
|
|
cifsInode = CIFS_I(old_file->d_inode);
|
|
if (rc == 0) {
|
|
old_file->d_inode->i_nlink++;
|
|
/* BB should we make this contingent on superblock flag NOATIME? */
|
|
/* old_file->d_inode->i_ctime = CURRENT_TIME;*/
|
|
/* parent dir timestamps will update from srv
|
|
within a second, would it really be worth it
|
|
to set the parent dir cifs inode time to zero
|
|
to force revalidate (faster) for it too? */
|
|
}
|
|
/* if not oplocked will force revalidate to get info
|
|
on source file from srv */
|
|
cifsInode->time = 0;
|
|
|
|
/* Will update parent dir timestamps from srv within a second.
|
|
Would it really be worth it to set the parent dir (cifs
|
|
inode) time field to zero to force revalidate on parent
|
|
directory faster ie
|
|
CIFS_I(inode)->time = 0; */
|
|
}
|
|
|
|
cifs_hl_exit:
|
|
kfree(fromName);
|
|
kfree(toName);
|
|
FreeXid(xid);
|
|
return rc;
|
|
}
|
|
|
|
void *
|
|
cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
|
|
{
|
|
struct inode *inode = direntry->d_inode;
|
|
int rc = -ENOMEM;
|
|
int xid;
|
|
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;
|
|
|
|
xid = GetXid();
|
|
|
|
/*
|
|
* For now, we just handle symlinks with unix extensions enabled.
|
|
* Eventually we should handle NTFS reparse points, and MacOS
|
|
* symlink support. For instance...
|
|
*
|
|
* rc = CIFSSMBQueryReparseLinkInfo(...)
|
|
*
|
|
* For now, just return -EACCES when the server doesn't support posix
|
|
* extensions. Note that we still allow querying symlinks when posix
|
|
* extensions are manually disabled. We could disable these as well
|
|
* but there doesn't seem to be any harm in allowing the client to
|
|
* read them.
|
|
*/
|
|
if (!(tcon->ses->capabilities & CAP_UNIX)) {
|
|
rc = -EACCES;
|
|
goto out;
|
|
}
|
|
|
|
full_path = build_path_from_dentry(direntry);
|
|
if (!full_path)
|
|
goto out;
|
|
|
|
cFYI(1, ("Full path: %s inode = 0x%p", full_path, inode));
|
|
|
|
rc = CIFSSMBUnixQuerySymLink(xid, tcon, full_path, &target_path,
|
|
cifs_sb->local_nls);
|
|
kfree(full_path);
|
|
out:
|
|
if (rc != 0) {
|
|
kfree(target_path);
|
|
target_path = ERR_PTR(rc);
|
|
}
|
|
|
|
FreeXid(xid);
|
|
nd_set_link(nd, target_path);
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
cifs_symlink(struct inode *inode, struct dentry *direntry, const char *symname)
|
|
{
|
|
int rc = -EOPNOTSUPP;
|
|
int xid;
|
|
struct cifs_sb_info *cifs_sb;
|
|
struct cifsTconInfo *pTcon;
|
|
char *full_path = NULL;
|
|
struct inode *newinode = NULL;
|
|
|
|
xid = GetXid();
|
|
|
|
cifs_sb = CIFS_SB(inode->i_sb);
|
|
pTcon = cifs_sb->tcon;
|
|
|
|
full_path = build_path_from_dentry(direntry);
|
|
|
|
if (full_path == NULL) {
|
|
FreeXid(xid);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
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)
|
|
rc = CIFSUnixCreateSymLink(xid, pTcon, full_path, symname,
|
|
cifs_sb->local_nls);
|
|
/* else
|
|
rc = CIFSCreateReparseSymLink(xid, pTcon, fromName, toName,
|
|
cifs_sb_target->local_nls); */
|
|
|
|
if (rc == 0) {
|
|
if (pTcon->unix_ext)
|
|
rc = cifs_get_inode_info_unix(&newinode, full_path,
|
|
inode->i_sb, xid);
|
|
else
|
|
rc = cifs_get_inode_info(&newinode, full_path, NULL,
|
|
inode->i_sb, xid, NULL);
|
|
|
|
if (rc != 0) {
|
|
cFYI(1, ("Create symlink ok, getinodeinfo fail rc = %d",
|
|
rc));
|
|
} else {
|
|
if (pTcon->nocase)
|
|
direntry->d_op = &cifs_ci_dentry_ops;
|
|
else
|
|
direntry->d_op = &cifs_dentry_ops;
|
|
d_instantiate(direntry, newinode);
|
|
}
|
|
}
|
|
|
|
kfree(full_path);
|
|
FreeXid(xid);
|
|
return rc;
|
|
}
|
|
|
|
void cifs_put_link(struct dentry *direntry, struct nameidata *nd, void *cookie)
|
|
{
|
|
char *p = nd_get_link(nd);
|
|
if (!IS_ERR(p))
|
|
kfree(p);
|
|
}
|