link.c 9.4 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3
/*
 *   fs/cifs/link.c
 *
S
Steve French 已提交
4
 *   Copyright (C) International Business Machines  Corp., 2002,2008
L
Linus Torvalds 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *   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>
23
#include <linux/slab.h>
L
Linus Torvalds 已提交
24 25 26 27 28 29 30
#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"
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
#include "md5.h"

#define CIFS_MF_SYMLINK_LEN_OFFSET (4+1)
#define CIFS_MF_SYMLINK_MD5_OFFSET (CIFS_MF_SYMLINK_LEN_OFFSET+(4+1))
#define CIFS_MF_SYMLINK_LINK_OFFSET (CIFS_MF_SYMLINK_MD5_OFFSET+(32+1))
#define CIFS_MF_SYMLINK_LINK_MAXLEN (1024)
#define CIFS_MF_SYMLINK_FILE_SIZE \
	(CIFS_MF_SYMLINK_LINK_OFFSET + CIFS_MF_SYMLINK_LINK_MAXLEN)

#define CIFS_MF_SYMLINK_LEN_FORMAT "XSym\n%04u\n"
#define CIFS_MF_SYMLINK_MD5_FORMAT \
	"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n"
#define CIFS_MF_SYMLINK_MD5_ARGS(md5_hash) \
	md5_hash[0],  md5_hash[1],  md5_hash[2],  md5_hash[3], \
	md5_hash[4],  md5_hash[5],  md5_hash[6],  md5_hash[7], \
	md5_hash[8],  md5_hash[9],  md5_hash[10], md5_hash[11],\
	md5_hash[12], md5_hash[13], md5_hash[14], md5_hash[15]

static int
CIFSParseMFSymlink(const u8 *buf,
		   unsigned int buf_len,
		   unsigned int *_link_len,
		   char **_link_str)
{
	int rc;
	unsigned int link_len;
	const char *md5_str1;
	const char *link_str;
	struct MD5Context md5_ctx;
	u8 md5_hash[16];
	char md5_str2[34];

	if (buf_len != CIFS_MF_SYMLINK_FILE_SIZE)
		return -EINVAL;

	md5_str1 = (const char *)&buf[CIFS_MF_SYMLINK_MD5_OFFSET];
	link_str = (const char *)&buf[CIFS_MF_SYMLINK_LINK_OFFSET];

	rc = sscanf(buf, CIFS_MF_SYMLINK_LEN_FORMAT, &link_len);
	if (rc != 1)
		return -EINVAL;

	cifs_MD5_init(&md5_ctx);
	cifs_MD5_update(&md5_ctx, (const u8 *)link_str, link_len);
	cifs_MD5_final(md5_hash, &md5_ctx);

	snprintf(md5_str2, sizeof(md5_str2),
		 CIFS_MF_SYMLINK_MD5_FORMAT,
		 CIFS_MF_SYMLINK_MD5_ARGS(md5_hash));

	if (strncmp(md5_str1, md5_str2, 17) != 0)
		return -EINVAL;

	if (_link_str) {
		*_link_str = kstrndup(link_str, link_len, GFP_KERNEL);
		if (!*_link_str)
			return -ENOMEM;
	}

	*_link_len = link_len;
	return 0;
}
L
Linus Torvalds 已提交
93

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
bool
CIFSCouldBeMFSymlink(const struct cifs_fattr *fattr)
{
	if (!(fattr->cf_mode & S_IFREG))
		/* it's not a symlink */
		return false;

	if (fattr->cf_eof != CIFS_MF_SYMLINK_FILE_SIZE)
		/* it's not a symlink */
		return false;

	return true;
}

int
CIFSCheckMFSymlink(struct cifs_fattr *fattr,
		   const unsigned char *path,
		   struct cifs_sb_info *cifs_sb, int xid)
{
	int rc;
	int oplock = 0;
	__u16 netfid = 0;
	struct cifsTconInfo *pTcon = cifs_sb->tcon;
	u8 *buf;
	char *pbuf;
	unsigned int bytes_read = 0;
	int buf_type = CIFS_NO_BUFFER;
	unsigned int link_len = 0;
	FILE_ALL_INFO file_info;

	if (!CIFSCouldBeMFSymlink(fattr))
		/* it's not a symlink */
		return 0;

	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;

	if (file_info.EndOfFile != CIFS_MF_SYMLINK_FILE_SIZE) {
		CIFSSMBClose(xid, pTcon, netfid);
		/* it's not a symlink */
		return 0;
	}

	buf = kmalloc(CIFS_MF_SYMLINK_FILE_SIZE, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;
	pbuf = buf;

	rc = CIFSSMBRead(xid, pTcon, netfid,
			 CIFS_MF_SYMLINK_FILE_SIZE /* length */,
			 0 /* offset */,
			 &bytes_read, &pbuf, &buf_type);
	CIFSSMBClose(xid, pTcon, netfid);
	if (rc != 0) {
		kfree(buf);
		return rc;
	}

	rc = CIFSParseMFSymlink(buf, bytes_read, &link_len, NULL);
	kfree(buf);
	if (rc == -EINVAL)
		/* it's not a symlink */
		return 0;
	if (rc != 0)
		return rc;

	/* 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;
}

L
Linus Torvalds 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
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) */

193 194
	fromName = build_path_from_dentry(old_file);
	toName = build_path_from_dentry(direntry);
S
Steve French 已提交
195
	if ((fromName == NULL) || (toName == NULL)) {
L
Linus Torvalds 已提交
196 197 198 199
		rc = -ENOMEM;
		goto cifs_hl_exit;
	}

200 201
/*	if (cifs_sb_target->tcon->ses->capabilities & CAP_UNIX)*/
	if (pTcon->unix_ext)
L
Linus Torvalds 已提交
202
		rc = CIFSUnixCreateHardLink(xid, pTcon, fromName, toName,
S
Steve French 已提交
203
					    cifs_sb_target->local_nls,
204 205
					    cifs_sb_target->mnt_cifs_flags &
						CIFS_MOUNT_MAP_SPECIAL_CHR);
L
Linus Torvalds 已提交
206 207
	else {
		rc = CIFSCreateHardLink(xid, pTcon, fromName, toName,
S
Steve French 已提交
208
					cifs_sb_target->local_nls,
209 210
					cifs_sb_target->mnt_cifs_flags &
						CIFS_MOUNT_MAP_SPECIAL_CHR);
S
Steve French 已提交
211 212
		if ((rc == -EIO) || (rc == -EINVAL))
			rc = -EOPNOTSUPP;
L
Linus Torvalds 已提交
213 214
	}

215 216 217 218
	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 */
S
Steve French 已提交
219
	if (old_file->d_inode) {
220
		cifsInode = CIFS_I(old_file->d_inode);
S
Steve French 已提交
221
		if (rc == 0) {
222
			old_file->d_inode->i_nlink++;
223 224
/* BB should we make this contingent on superblock flag NOATIME? */
/*			old_file->d_inode->i_ctime = CURRENT_TIME;*/
225 226 227 228 229
			/* 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? */
		}
S
Steve French 已提交
230
		/* if not oplocked will force revalidate to get info
231 232 233
		   on source file from srv */
		cifsInode->time = 0;

S
Steve French 已提交
234
		/* Will update parent dir timestamps from srv within a second.
235 236
		   Would it really be worth it to set the parent dir (cifs
		   inode) time field to zero to force revalidate on parent
S
Steve French 已提交
237
		   directory faster ie
238
			CIFS_I(inode)->time = 0;  */
L
Linus Torvalds 已提交
239 240 241
	}

cifs_hl_exit:
J
Jesper Juhl 已提交
242 243
	kfree(fromName);
	kfree(toName);
L
Linus Torvalds 已提交
244 245 246 247
	FreeXid(xid);
	return rc;
}

248
void *
L
Linus Torvalds 已提交
249 250 251
cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
{
	struct inode *inode = direntry->d_inode;
252
	int rc = -ENOMEM;
L
Linus Torvalds 已提交
253 254
	int xid;
	char *full_path = NULL;
255 256 257
	char *target_path = NULL;
	struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
	struct cifsTconInfo *tcon = cifs_sb->tcon;
L
Linus Torvalds 已提交
258 259 260

	xid = GetXid();

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
	/*
	 * 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;
	}
L
Linus Torvalds 已提交
278

279
	full_path = build_path_from_dentry(direntry);
L
Linus Torvalds 已提交
280
	if (!full_path)
281
		goto out;
L
Linus Torvalds 已提交
282

283
	cFYI(1, "Full path: %s inode = 0x%p", full_path, inode);
L
Linus Torvalds 已提交
284

285 286 287 288
	rc = CIFSSMBUnixQuerySymLink(xid, tcon, full_path, &target_path,
				     cifs_sb->local_nls);
	kfree(full_path);
out:
289
	if (rc != 0) {
L
Linus Torvalds 已提交
290 291 292 293 294 295
		kfree(target_path);
		target_path = ERR_PTR(rc);
	}

	FreeXid(xid);
	nd_set_link(nd, target_path);
296
	return NULL;
L
Linus Torvalds 已提交
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
}

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;

314
	full_path = build_path_from_dentry(direntry);
L
Linus Torvalds 已提交
315

S
Steve French 已提交
316
	if (full_path == NULL) {
317
		rc = -ENOMEM;
L
Linus Torvalds 已提交
318
		FreeXid(xid);
319
		return rc;
L
Linus Torvalds 已提交
320 321
	}

322 323
	cFYI(1, "Full path: %s", full_path);
	cFYI(1, "symname is %s", symname);
L
Linus Torvalds 已提交
324 325

	/* BB what if DFS and this volume is on different share? BB */
326
	if (pTcon->unix_ext)
L
Linus Torvalds 已提交
327 328 329
		rc = CIFSUnixCreateSymLink(xid, pTcon, full_path, symname,
					   cifs_sb->local_nls);
	/* else
S
Steve French 已提交
330 331
	   rc = CIFSCreateReparseSymLink(xid, pTcon, fromName, toName,
					cifs_sb_target->local_nls); */
L
Linus Torvalds 已提交
332 333

	if (rc == 0) {
334
		if (pTcon->unix_ext)
L
Linus Torvalds 已提交
335
			rc = cifs_get_inode_info_unix(&newinode, full_path,
S
Steve French 已提交
336
						      inode->i_sb, xid);
L
Linus Torvalds 已提交
337 338
		else
			rc = cifs_get_inode_info(&newinode, full_path, NULL,
339
						 inode->i_sb, xid, NULL);
L
Linus Torvalds 已提交
340 341

		if (rc != 0) {
342 343
			cFYI(1, "Create symlink ok, getinodeinfo fail rc = %d",
			      rc);
L
Linus Torvalds 已提交
344
		} else {
345 346 347 348
			if (pTcon->nocase)
				direntry->d_op = &cifs_ci_dentry_ops;
			else
				direntry->d_op = &cifs_dentry_ops;
L
Linus Torvalds 已提交
349 350 351 352
			d_instantiate(direntry, newinode);
		}
	}

J
Jesper Juhl 已提交
353
	kfree(full_path);
L
Linus Torvalds 已提交
354 355 356 357
	FreeXid(xid);
	return rc;
}

358
void cifs_put_link(struct dentry *direntry, struct nameidata *nd, void *cookie)
L
Linus Torvalds 已提交
359 360 361 362 363
{
	char *p = nd_get_link(nd);
	if (!IS_ERR(p))
		kfree(p);
}