diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2010-08-03 14:33:09 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-08-03 14:33:09 -0700 |
commit | 54161df1fb1469d66bce3a3b14d8281adbb69263 (patch) | |
tree | b5f0f8741db382daf09dd236e9e678d60422dc48 | |
parent | be82ae0238b0453afcf4a76f0512b7dde34ba500 (diff) | |
parent | cb76d5e25008b76fb8e348c861d32659430ac3fa (diff) |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/sfrench/cifs-2.6
* git://git.kernel.org/pub/scm/linux/kernel/git/sfrench/cifs-2.6: (29 commits)
cifs: fsc should not default to "on"
[CIFS] remove redundant path walking in dfs_do_refmount
cifs: ignore the "mand", "nomand" and "_netdev" mount options
cifs: map NT_STATUS_ERROR_WRITE_PROTECTED to -EROFS
cifs: don't allow cifs_iget to match inodes of the wrong type
[CIFS] relinquish fscache cookie before freeing CIFSTconInfo
cifs: add separate cred_uid field to sesInfo
fs: cifs: check kmalloc() result
[CIFS] Missing ifdef
[CIFS] Missing line from previous commit
[CIFS] Fix build break when CONFIG_CIFS_FSCACHE disabled
cifs: add mount option to enable local caching
cifs: read pages from FS-Cache
cifs: store pages into local cache
cifs: FS-Cache page management
cifs: define inode-level cache object and register them
cifs: define superblock-level cache index objects and register them
cifs: remove unused cifsUidInfo struct
cifs: clean up cifs_find_smb_ses (try #2)
cifs: match secType when searching for existing tcp session
...
-rw-r--r-- | fs/cifs/Kconfig | 9 | ||||
-rw-r--r-- | fs/cifs/Makefile | 2 | ||||
-rw-r--r-- | fs/cifs/cache.c | 331 | ||||
-rw-r--r-- | fs/cifs/cifs_dfs_ref.c | 28 | ||||
-rw-r--r-- | fs/cifs/cifs_fs_sb.h | 1 | ||||
-rw-r--r-- | fs/cifs/cifs_spnego.c | 3 | ||||
-rw-r--r-- | fs/cifs/cifsfs.c | 15 | ||||
-rw-r--r-- | fs/cifs/cifsfs.h | 2 | ||||
-rw-r--r-- | fs/cifs/cifsglob.h | 40 | ||||
-rw-r--r-- | fs/cifs/cifsproto.h | 4 | ||||
-rw-r--r-- | fs/cifs/connect.c | 180 | ||||
-rw-r--r-- | fs/cifs/dir.c | 6 | ||||
-rw-r--r-- | fs/cifs/dns_resolve.c | 2 | ||||
-rw-r--r-- | fs/cifs/file.c | 74 | ||||
-rw-r--r-- | fs/cifs/fscache.c | 236 | ||||
-rw-r--r-- | fs/cifs/fscache.h | 136 | ||||
-rw-r--r-- | fs/cifs/inode.c | 20 | ||||
-rw-r--r-- | fs/cifs/ioctl.c | 3 | ||||
-rw-r--r-- | fs/cifs/netmisc.c | 24 | ||||
-rw-r--r-- | fs/cifs/readdir.c | 5 | ||||
-rw-r--r-- | fs/cifs/smberr.h | 1 |
21 files changed, 1006 insertions, 116 deletions
diff --git a/fs/cifs/Kconfig b/fs/cifs/Kconfig index 80f35259680..5739fd7f88b 100644 --- a/fs/cifs/Kconfig +++ b/fs/cifs/Kconfig @@ -131,6 +131,15 @@ config CIFS_DFS_UPCALL IP addresses) which is needed for implicit mounts of DFS junction points. If unsure, say N. +config CIFS_FSCACHE + bool "Provide CIFS client caching support (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on CIFS=m && FSCACHE || CIFS=y && FSCACHE=y + help + Makes CIFS FS-Cache capable. Say Y here if you want your CIFS data + to be cached locally on disk through the general filesystem cache + manager. If unsure, say N. + config CIFS_EXPERIMENTAL bool "CIFS Experimental Features (EXPERIMENTAL)" depends on CIFS && EXPERIMENTAL diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile index 9948c0030e8..adefa60a9bd 100644 --- a/fs/cifs/Makefile +++ b/fs/cifs/Makefile @@ -11,3 +11,5 @@ cifs-y := cifsfs.o cifssmb.o cifs_debug.o connect.o dir.o file.o inode.o \ cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o + +cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o diff --git a/fs/cifs/cache.c b/fs/cifs/cache.c new file mode 100644 index 00000000000..224d7bbd1fc --- /dev/null +++ b/fs/cifs/cache.c @@ -0,0 +1,331 @@ +/* + * fs/cifs/cache.c - CIFS filesystem cache index structure definitions + * + * Copyright (c) 2010 Novell, Inc. + * Authors(s): Suresh Jayaraman (sjayaraman@suse.de> + * + * 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 "fscache.h" +#include "cifs_debug.h" + +/* + * CIFS filesystem definition for FS-Cache + */ +struct fscache_netfs cifs_fscache_netfs = { + .name = "cifs", + .version = 0, +}; + +/* + * Register CIFS for caching with FS-Cache + */ +int cifs_fscache_register(void) +{ + return fscache_register_netfs(&cifs_fscache_netfs); +} + +/* + * Unregister CIFS for caching + */ +void cifs_fscache_unregister(void) +{ + fscache_unregister_netfs(&cifs_fscache_netfs); +} + +/* + * Key layout of CIFS server cache index object + */ +struct cifs_server_key { + uint16_t family; /* address family */ + uint16_t port; /* IP port */ + union { + struct in_addr ipv4_addr; + struct in6_addr ipv6_addr; + } addr[0]; +}; + +/* + * Server object keyed by {IPaddress,port,family} tuple + */ +static uint16_t cifs_server_get_key(const void *cookie_netfs_data, + void *buffer, uint16_t maxbuf) +{ + const struct TCP_Server_Info *server = cookie_netfs_data; + const struct sockaddr *sa = (struct sockaddr *) &server->addr.sockAddr; + struct cifs_server_key *key = buffer; + uint16_t key_len = sizeof(struct cifs_server_key); + + memset(key, 0, key_len); + + /* + * Should not be a problem as sin_family/sin6_family overlays + * sa_family field + */ + switch (sa->sa_family) { + case AF_INET: + key->family = server->addr.sockAddr.sin_family; + key->port = server->addr.sockAddr.sin_port; + key->addr[0].ipv4_addr = server->addr.sockAddr.sin_addr; + key_len += sizeof(key->addr[0].ipv4_addr); + break; + + case AF_INET6: + key->family = server->addr.sockAddr6.sin6_family; + key->port = server->addr.sockAddr6.sin6_port; + key->addr[0].ipv6_addr = server->addr.sockAddr6.sin6_addr; + key_len += sizeof(key->addr[0].ipv6_addr); + break; + + default: + cERROR(1, "CIFS: Unknown network family '%d'", sa->sa_family); + key_len = 0; + break; + } + + return key_len; +} + +/* + * Server object for FS-Cache + */ +const struct fscache_cookie_def cifs_fscache_server_index_def = { + .name = "CIFS.server", + .type = FSCACHE_COOKIE_TYPE_INDEX, + .get_key = cifs_server_get_key, +}; + +/* + * Auxiliary data attached to CIFS superblock within the cache + */ +struct cifs_fscache_super_auxdata { + u64 resource_id; /* unique server resource id */ +}; + +static char *extract_sharename(const char *treename) +{ + const char *src; + char *delim, *dst; + int len; + + /* skip double chars at the beginning */ + src = treename + 2; + + /* share name is always preceded by '\\' now */ + delim = strchr(src, '\\'); + if (!delim) + return ERR_PTR(-EINVAL); + delim++; + len = strlen(delim); + + /* caller has to free the memory */ + dst = kstrndup(delim, len, GFP_KERNEL); + if (!dst) + return ERR_PTR(-ENOMEM); + + return dst; +} + +/* + * Superblock object currently keyed by share name + */ +static uint16_t cifs_super_get_key(const void *cookie_netfs_data, void *buffer, + uint16_t maxbuf) +{ + const struct cifsTconInfo *tcon = cookie_netfs_data; + char *sharename; + uint16_t len; + + sharename = extract_sharename(tcon->treeName); + if (IS_ERR(sharename)) { + cFYI(1, "CIFS: couldn't extract sharename\n"); + sharename = NULL; + return 0; + } + + len = strlen(sharename); + if (len > maxbuf) + return 0; + + memcpy(buffer, sharename, len); + + kfree(sharename); + + return len; +} + +static uint16_t +cifs_fscache_super_get_aux(const void *cookie_netfs_data, void *buffer, + uint16_t maxbuf) +{ + struct cifs_fscache_super_auxdata auxdata; + const struct cifsTconInfo *tcon = cookie_netfs_data; + + memset(&auxdata, 0, sizeof(auxdata)); + auxdata.resource_id = tcon->resource_id; + + if (maxbuf > sizeof(auxdata)) + maxbuf = sizeof(auxdata); + + memcpy(buffer, &auxdata, maxbuf); + + return maxbuf; +} + +static enum +fscache_checkaux cifs_fscache_super_check_aux(void *cookie_netfs_data, + const void *data, + uint16_t datalen) +{ + struct cifs_fscache_super_auxdata auxdata; + const struct cifsTconInfo *tcon = cookie_netfs_data; + + if (datalen != sizeof(auxdata)) + return FSCACHE_CHECKAUX_OBSOLETE; + + memset(&auxdata, 0, sizeof(auxdata)); + auxdata.resource_id = tcon->resource_id; + + if (memcmp(data, &auxdata, datalen) != 0) + return FSCACHE_CHECKAUX_OBSOLETE; + + return FSCACHE_CHECKAUX_OKAY; +} + +/* + * Superblock object for FS-Cache + */ +const struct fscache_cookie_def cifs_fscache_super_index_def = { + .name = "CIFS.super", + .type = FSCACHE_COOKIE_TYPE_INDEX, + .get_key = cifs_super_get_key, + .get_aux = cifs_fscache_super_get_aux, + .check_aux = cifs_fscache_super_check_aux, +}; + +/* + * Auxiliary data attached to CIFS inode within the cache + */ +struct cifs_fscache_inode_auxdata { + struct timespec last_write_time; + struct timespec last_change_time; + u64 eof; +}; + +static uint16_t cifs_fscache_inode_get_key(const void *cookie_netfs_data, + void *buffer, uint16_t maxbuf) +{ + const struct cifsInodeInfo *cifsi = cookie_netfs_data; + uint16_t keylen; + + /* use the UniqueId as the key */ + keylen = sizeof(cifsi->uniqueid); + if (keylen > maxbuf) + keylen = 0; + else + memcpy(buffer, &cifsi->uniqueid, keylen); + + return keylen; +} + +static void +cifs_fscache_inode_get_attr(const void *cookie_netfs_data, uint64_t *size) +{ + const struct cifsInodeInfo *cifsi = cookie_netfs_data; + + *size = cifsi->vfs_inode.i_size; +} + +static uint16_t +cifs_fscache_inode_get_aux(const void *cookie_netfs_data, void *buffer, + uint16_t maxbuf) +{ + struct cifs_fscache_inode_auxdata auxdata; + const struct cifsInodeInfo *cifsi = cookie_netfs_data; + + memset(&auxdata, 0, sizeof(auxdata)); + auxdata.eof = cifsi->server_eof; + auxdata.last_write_time = cifsi->vfs_inode.i_mtime; + auxdata.last_change_time = cifsi->vfs_inode.i_ctime; + + if (maxbuf > sizeof(auxdata)) + maxbuf = sizeof(auxdata); + + memcpy(buffer, &auxdata, maxbuf); + + return maxbuf; +} + +static enum +fscache_checkaux cifs_fscache_inode_check_aux(void *cookie_netfs_data, + const void *data, + uint16_t datalen) +{ + struct cifs_fscache_inode_auxdata auxdata; + struct cifsInodeInfo *cifsi = cookie_netfs_data; + + if (datalen != sizeof(auxdata)) + return FSCACHE_CHECKAUX_OBSOLETE; + + memset(&auxdata, 0, sizeof(auxdata)); + auxdata.eof = cifsi->server_eof; + auxdata.last_write_time = cifsi->vfs_inode.i_mtime; + auxdata.last_change_time = cifsi->vfs_inode.i_ctime; + + if (memcmp(data, &auxdata, datalen) != 0) + return FSCACHE_CHECKAUX_OBSOLETE; + + return FSCACHE_CHECKAUX_OKAY; +} + +static void cifs_fscache_inode_now_uncached(void *cookie_netfs_data) +{ + struct cifsInodeInfo *cifsi = cookie_netfs_data; + struct pagevec pvec; + pgoff_t first; + int loop, nr_pages; + + pagevec_init(&pvec, 0); + first = 0; + + cFYI(1, "cifs inode 0x%p now uncached", cifsi); + + for (;;) { + nr_pages = pagevec_lookup(&pvec, + cifsi->vfs_inode.i_mapping, first, + PAGEVEC_SIZE - pagevec_count(&pvec)); + if (!nr_pages) + break; + + for (loop = 0; loop < nr_pages; loop++) + ClearPageFsCache(pvec.pages[loop]); + + first = pvec.pages[nr_pages - 1]->index + 1; + + pvec.nr = nr_pages; + pagevec_release(&pvec); + cond_resched(); + } +} + +const struct fscache_cookie_def cifs_fscache_inode_object_def = { + .name = "CIFS.uniqueid", + .type = FSCACHE_COOKIE_TYPE_DATAFILE, + .get_key = cifs_fscache_inode_get_key, + .get_attr = cifs_fscache_inode_get_attr, + .get_aux = cifs_fscache_inode_get_aux, + .check_aux = cifs_fscache_inode_check_aux, + .now_uncached = cifs_fscache_inode_now_uncached, +}; diff --git a/fs/cifs/cifs_dfs_ref.c b/fs/cifs/cifs_dfs_ref.c index ac19a6f3dae..dc1ed50ea06 100644 --- a/fs/cifs/cifs_dfs_ref.c +++ b/fs/cifs/cifs_dfs_ref.c @@ -230,28 +230,22 @@ compose_mount_options_err: goto compose_mount_options_out; } - -static struct vfsmount *cifs_dfs_do_refmount(const struct vfsmount *mnt_parent, - struct dentry *dentry, const struct dfs_info3_param *ref) +/** + * cifs_dfs_do_refmount - mounts specified path using provided refferal + * @cifs_sb: parent/root superblock + * @fullpath: full path in UNC format + * @ref: server's referral + */ +static struct vfsmount *cifs_dfs_do_refmount(struct cifs_sb_info *cifs_sb, + const char *fullpath, const struct dfs_info3_param *ref) { - struct cifs_sb_info *cifs_sb; struct vfsmount *mnt; char *mountdata; char *devname = NULL; - char *fullpath; - - cifs_sb = CIFS_SB(dentry->d_inode->i_sb); - /* - * this function gives us a path with a double backslash prefix. We - * require a single backslash for DFS. - */ - fullpath = build_path_from_dentry(dentry); - if (!fullpath) - return ERR_PTR(-ENOMEM); + /* strip first '\' from fullpath */ mountdata = cifs_compose_mount_options(cifs_sb->mountdata, fullpath + 1, ref, &devname); - kfree(fullpath); if (IS_ERR(mountdata)) return (struct vfsmount *)mountdata; @@ -357,8 +351,8 @@ cifs_dfs_follow_mountpoint(struct dentry *dentry, struct nameidata *nd) rc = -EINVAL; goto out_err; } - mnt = cifs_dfs_do_refmount(nd->path.mnt, - nd->path.dentry, referrals + i); + mnt = cifs_dfs_do_refmount(cifs_sb, + full_path, referrals + i); cFYI(1, "%s: cifs_dfs_do_refmount:%s , mnt:%p", __func__, referrals[i].node_name, mnt); diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h index 246a167cb91..9e771450c3b 100644 --- a/fs/cifs/cifs_fs_sb.h +++ b/fs/cifs/cifs_fs_sb.h @@ -35,6 +35,7 @@ #define CIFS_MOUNT_DYNPERM 0x1000 /* allow in-memory only mode setting */ #define CIFS_MOUNT_NOPOSIXBRL 0x2000 /* mandatory not posix byte range lock */ #define CIFS_MOUNT_NOSSYNC 0x4000 /* don't do slow SMBflush on every sync*/ +#define CIFS_MOUNT_FSCACHE 0x8000 /* local caching enabled */ struct cifs_sb_info { struct cifsTconInfo *tcon; /* primary mount */ diff --git a/fs/cifs/cifs_spnego.c b/fs/cifs/cifs_spnego.c index 379bd7d9c05..6effccff85a 100644 --- a/fs/cifs/cifs_spnego.c +++ b/fs/cifs/cifs_spnego.c @@ -144,6 +144,9 @@ cifs_get_spnego_key(struct cifsSesInfo *sesInfo) sprintf(dp, ";uid=0x%x", sesInfo->linux_uid); dp = description + strlen(description); + sprintf(dp, ";creduid=0x%x", sesInfo->cred_uid); + + dp = description + strlen(description); sprintf(dp, ";user=%s", sesInfo->userName); dp = description + strlen(description); diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 2cb1a70214d..8a2cf129e53 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -47,6 +47,7 @@ #include <linux/key-type.h> #include "dns_resolve.h" #include "cifs_spnego.h" +#include "fscache.h" #define CIFS_MAGIC_NUMBER 0xFF534D42 /* the first four bytes of SMB PDUs */ int cifsFYI = 0; @@ -329,6 +330,12 @@ cifs_destroy_inode(struct inode *inode) } static void +cifs_clear_inode(struct inode *inode) +{ + cifs_fscache_release_inode_cookie(inode); +} + +static void cifs_show_address(struct seq_file *s, struct TCP_Server_Info *server) { seq_printf(s, ",addr="); @@ -489,6 +496,7 @@ static const struct super_operations cifs_super_ops = { .alloc_inode = cifs_alloc_inode, .destroy_inode = cifs_destroy_inode, .drop_inode = cifs_drop_inode, + .clear_inode = cifs_clear_inode, /* .delete_inode = cifs_delete_inode, */ /* Do not need above function unless later we add lazy close of inodes or unless the kernel forgets to call us with the same number of releases (closes) @@ -902,6 +910,10 @@ init_cifs(void) cFYI(1, "cifs_max_pending set to max of 256"); } + rc = cifs_fscache_register(); + if (rc) + goto out; + rc = cifs_init_inodecache(); if (rc) goto out_clean_proc; @@ -951,6 +963,8 @@ init_cifs(void) cifs_destroy_inodecache(); out_clean_proc: cifs_proc_clean(); + cifs_fscache_unregister(); + out: return rc; } @@ -959,6 +973,7 @@ exit_cifs(void) { cFYI(DBG2, "exit_cifs"); cifs_proc_clean(); + cifs_fscache_unregister(); #ifdef CONFIG_CIFS_DFS_UPCALL cifs_dfs_release_automount_timer(); cifs_exit_dns_resolver(); diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h index a7eb65c84b1..d82f5fb4761 100644 --- a/fs/cifs/cifsfs.h +++ b/fs/cifs/cifsfs.h @@ -114,5 +114,5 @@ extern long cifs_ioctl(struct file *filep, unsigned int cmd, unsigned long arg); extern const struct export_operations cifs_export_ops; #endif /* EXPERIMENTAL */ -#define CIFS_VERSION "1.64" +#define CIFS_VERSION "1.65" #endif /* _CIFSFS_H */ diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index a88479ceaad..59906146ad3 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -16,6 +16,9 @@ * the GNU Lesser General Public License for more details. * */ +#ifndef _CIFS_GLOB_H +#define _CIFS_GLOB_H + #include <linux/in.h> #include <linux/in6.h> #include <linux/slab.h> @@ -34,7 +37,7 @@ #define MAX_SHARE_SIZE 64 /* used to be 20, this should still be enough */ #define MAX_USERNAME_SIZE 32 /* 32 is to allow for 15 char names + null termination then *2 for unicode versions */ -#define MAX_PASSWORD_SIZE 16 +#define MAX_PASSWORD_SIZE 512 /* max for windows seems to be 256 wide chars */ #define CIFS_MIN_RCV_POOL 4 @@ -80,8 +83,7 @@ enum statusEnum { }; enum securityEnum { - PLAINTXT = 0, /* Legacy with Plaintext passwords */ - LANMAN, /* Legacy LANMAN auth */ + LANMAN = 0, /* Legacy LANMAN auth */ NTLM, /* Legacy NTLM012 auth with NTLM hash */ NTLMv2, /* Legacy NTLM auth with NTLMv2 hash */ RawNTLMSSP, /* NTLMSSP without SPNEGO, NTLMv2 hash */ @@ -142,7 +144,6 @@ struct TCP_Server_Info { struct list_head pending_mid_q; void *Server_NlsInfo; /* BB - placeholder for future NLS info */ unsigned short server_codepage; /* codepage for the server */ - unsigned long ip_address; /* IP addr for the server if known */ enum protocolEnum protocolType; char versionMajor; char versionMinor; @@ -190,19 +191,9 @@ struct TCP_Server_Info { bool sec_mskerberos; /* supports legacy MS Kerberos */ bool sec_kerberosu2u; /* supports U2U Kerberos */ bool sec_ntlmssp; /* supports NTLMSSP */ -}; - -/* - * The following is our shortcut to user information. We surface the uid, - * and name. We always get the password on the fly in case it - * has changed. We also hang a list of sessions owned by this user off here. - */ -struct cifsUidInfo { - struct list_head userList; - struct list_head sessionList; /* SMB sessions for this user */ - uid_t linux_uid; - char user[MAX_USERNAME_SIZE + 1]; /* ascii name of user */ - /* BB may need ptr or callback for PAM or WinBind info */ +#ifdef CONFIG_CIFS_FSCACHE + struct fscache_cookie *fscache; /* client index cache cookie */ +#endif }; /* @@ -212,9 +203,6 @@ struct cifsSesInfo { struct list_head smb_ses_list; struct list_head tcon_list; struct mutex session_mutex; -#if 0 - struct cifsUidInfo *uidInfo; /* pointer to user info */ -#endif struct TCP_Server_Info *server; /* pointer to server info */ int ses_count; /* reference counter */ enum statusEnum status; @@ -226,7 +214,8 @@ struct cifsSesInfo { char *serverNOS; /* name of network operating system of server */ char *serverDomain; /* security realm of server */ int Suid; /* remote smb uid */ - uid_t linux_uid; /* local Linux uid */ + uid_t linux_uid; /* overriding owner of files on the mount */ + uid_t cred_uid; /* owner of credentials */ int capabilities; char serverName[SERVER_NAME_LEN_WITH_NULL * 2]; /* BB make bigger for TCP names - will ipv6 and sctp addresses fit? */ @@ -311,6 +300,10 @@ struct cifsTconInfo { bool local_lease:1; /* check leases (only) on local system not remote */ bool broken_posix_open; /* e.g. Samba server versions < 3.3.2, 3.2.9 */ bool need_reconnect:1; /* connection reset, tid now invalid */ +#ifdef CONFIG_CIFS_FSCACHE + u64 resource_id; /* server resource id */ + struct fscache_cookie *fscache; /* cookie for share */ +#endif /* BB add field for back pointer to sb struct(s)? */ }; @@ -398,6 +391,9 @@ struct cifsInodeInfo { bool invalid_mapping:1; /* pagecache is invalid */ u64 server_eof; /* current file size on server */ u64 uniqueid; /* server inode number */ +#ifdef CONFIG_CIFS_FSCACHE + struct fscache_cookie *fscache; +#endif struct inode vfs_inode; }; @@ -733,3 +729,5 @@ GLOBAL_EXTERN unsigned int cifs_min_small; /* min size of small buf pool */ GLOBAL_EXTERN unsigned int cifs_max_pending; /* MAX requests at once to server*/ extern const struct slow_work_ops cifs_oplock_break_ops; + +#endif /* _CIFS_GLOB_H */ diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index fb6318b8150..2eaebbd3113 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -86,7 +86,9 @@ extern unsigned int smbCalcSize(struct smb_hdr *ptr); extern unsigned int smbCalcSize_LE(struct smb_hdr *ptr); extern int decode_negTokenInit(unsigned char *security_blob, int length, struct TCP_Server_Info *server); -extern int cifs_convert_address(char *src, void *dst); +extern int cifs_convert_address(struct sockaddr *dst, char *src); +extern int cifs_fill_sockaddr(struct sockaddr *dst, char *src, + unsigned short int port); extern int map_smb_to_linux_error(struct smb_hdr *smb, int logErr); extern void header_assemble(struct smb_hdr *, char /* command */ , const struct cifsTconInfo *, int /* length of diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 2208f06e4c4..2a43a0aca96 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -48,6 +48,7 @@ #include "nterr.h" #include "rfc1002pdu.h" #include "cn_cifs.h" +#include "fscache.h" #define CIFS_PORT 445 #define RFC1001_PORT 139 @@ -66,6 +67,7 @@ struct smb_vol { char *iocharset; /* local code page for mapping to and from Unicode */ char source_rfc1001_name[16]; /* netbios name of client */ char target_rfc1001_name[16]; /* netbios name of server for Win9x/ME */ + uid_t cred_uid; uid_t linux_uid; gid_t linux_gid; mode_t file_mode; @@ -97,6 +99,7 @@ struct smb_vol { bool noblocksnd:1; bool noautotune:1; bool nostrictsync:1; /* do not force expensive SMBflush on every sync */ + bool fsc:1; /* enable fscache */ unsigned int rsize; unsigned int wsize; bool sockopt_tcp_nodelay:1; @@ -830,7 +833,8 @@ cifs_parse_mount_options(char *options, const char *devname, /* null target name indicates to use *SMBSERVR default called name if we end up sending RFC1001 session initialize */ vol->target_rfc1001_name[0] = 0; - vol->linux_uid = current_uid(); /* use current_euid() instead? */ + vol->cred_uid = current_uid(); + vol->linux_uid = current_uid(); vol->linux_gid = current_gid(); /* default to only allowing write access to owner of the mount */ @@ -1257,6 +1261,12 @@ cifs_parse_mount_options(char *options, const char *devname, } else if ((strnicmp(data, "nocase", 6) == 0) || (strnicmp(data, "ignorecase", 10) == 0)) { vol->nocase = 1; + } else if (strnicmp(data, "mand", 4) == 0) { + /* ignore */ + } else if (strnicmp(data, "nomand", 6) == 0) { + /* ignore */ + } else if (strnicmp(data, "_netdev", 7) == 0) { + /* ignore */ } else if (strnicmp(data, "brl", 3) == 0) { vol->nobrl = 0; } else if ((strnicmp(data, "nobrl", 5) == 0) || @@ -1331,6 +1341,8 @@ cifs_parse_mount_options(char *options, const char *devname, printk(KERN_WARNING "CIFS: Mount option noac not " "supported. Instead set " "/proc/fs/cifs/LookupCacheEnabled to 0\n"); + } else if (strnicmp(data, "fsc", 3) == 0) { + vol->fsc = true; } else printk(KERN_WARNING "CIFS: Unknown mount option %s\n", data); @@ -1380,18 +1392,92 @@ cifs_parse_mount_options(char *options, const char *devname, return 0; } +static bool +match_address(struct TCP_Server_Info *server, struct sockaddr *addr) +{ + struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; + + switch (addr->sa_family) { + case AF_INET: + if (addr4->sin_addr.s_addr != + server->addr.sockAddr.sin_addr.s_addr) + return false; + if (addr4->sin_port && + addr4->sin_port != server->addr.sockAddr.sin_port) + return false; + break; + case AF_INET6: + if (!ipv6_addr_equal(&addr6->sin6_addr, + &server->addr.sockAddr6.sin6_addr)) + return false; + if (addr6->sin6_scope_id != + server->addr.sockAddr6.sin6_scope_id) + return false; + if (addr6->sin6_port && + addr6->sin6_port != server->addr.sockAddr6.sin6_port) + return false; + break; + } + + return true; +} + +static bool +match_security(struct TCP_Server_Info *server, struct smb_vol *vol) +{ + unsigned int secFlags; + + if (vol->secFlg & (~(CIFSSEC_MUST_SIGN | CIFSSEC_MUST_SEAL))) + secFlags = vol->secFlg; + else + secFlags = global_secflags | vol->secFlg; + + switch (server->secType) { + case LANMAN: + if (!(secFlags & (CIFSSEC_MAY_LANMAN|CIFSSEC_MAY_PLNTXT))) + return false; + break; + case NTLMv2: + if (!(secFlags & CIFSSEC_MAY_NTLMV2)) + return false; + break; + case NTLM: + if (!(secFlags & CIFSSEC_MAY_NTLM)) + return false; + break; + case Kerberos: + if (!(secFlags & CIFSSEC_MAY_KRB5)) + return false; + break; + case RawNTLMSSP: + if (!(secFlags & CIFSSEC_MAY_NTLMSSP)) + return false; + break; + default: + /* shouldn't happen */ + return false; + } + + /* now check if signing mode is acceptible */ + if ((secFlags & CIFSSEC_MAY_SIGN) == 0 && + (server->secMode & SECMODE_SIGN_REQUIRED)) + return false; + else if (((secFlags & CIFSSEC_MUST_SIGN) == CIFSSEC_MUST_SIGN) && + (server->secMode & + (SECMODE_SIGN_ENABLED|SECMODE_SIGN_REQUIRED)) == 0) + return false; + + return true; +} + static struct TCP_Server_Info * -cifs_find_tcp_session(struct sockaddr_storage *addr, unsigned short int port) +cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol) { - struct list_head *tmp; struct TCP_Server_Info *server; - struct sockaddr_in *addr4 = (struct sockaddr_in *) addr; - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) addr; write_lock(&cifs_tcp_ses_lock); - list_for_each(tmp, &cifs_tcp_ses_list) { - server = list_entry(tmp, struct TCP_Server_Info, - tcp_ses_list); + list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { /* * the demux thread can exit on its own while still in CifsNew * so don't accept any sockets in that state. Since the @@ -1401,37 +1487,11 @@ cifs_find_tcp_session(struct sockaddr_storage *addr, unsigned short int port) if (server->tcpStatus == CifsNew) continue; - switch (addr->ss_family) { - case AF_INET: - if (addr4->sin_addr.s_addr == - server->addr.sockAddr.sin_addr.s_addr) { - addr4->sin_port = htons(port); - /* user overrode default port? */ - if (addr4->sin_port) { - if (addr4->sin_port != - server->addr.sockAddr.sin_port) - continue; - } - break; - } else - continue; + if (!match_address(server, addr)) + continue; - case AF_INET6: - if (ipv6_addr_equal(&addr6->sin6_addr, - &server->addr.sockAddr6.sin6_addr) && - (addr6->sin6_scope_id == - server->addr.sockAddr6.sin6_scope_id)) { - addr6->sin6_port = htons(port); - /* user overrode default port? */ - if (addr6->sin6_port) { - if (addr6->sin6_port != - server->addr.sockAddr6.sin6_port) - continue; - } - break; - } else - continue; - } + if (!match_security(server, vol)) + continue; ++server->srv_count; write_unlock(&cifs_tcp_ses_lock); @@ -1460,6 +1520,8 @@ cifs_put_tcp_session(struct TCP_Server_Info *server) server->tcpStatus = CifsExiting; spin_unlock(&GlobalMid_Lock); + cifs_fscache_release_client_cookie(server); + task = xchg(&server->tsk, NULL); if (task) force_sig(SIGKILL, task); @@ -1479,7 +1541,9 @@ cifs_get_tcp_session(struct smb_vol *volume_info) cFYI(1, "UNC: %s ip: %s", volume_info->UNC, volume_info->UNCip); if (volume_info->UNCip && volume_info->UNC) { - rc = cifs_convert_address(volume_info->UNCip, &addr); + rc = cifs_fill_sockaddr((struct sockaddr *)&addr, + volume_info->UNCip, + volume_info->port); if (!rc) { /* we failed translating address */ rc = -EINVAL; @@ -1499,7 +1563,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info) } /* see if we already have a matching tcp_ses */ - tcp_ses = cifs_find_tcp_session(&addr, volume_info->port); + tcp_ses = cifs_find_tcp_session((struct sockaddr *)&addr, volume_info); if (tcp_ses) return tcp_ses; @@ -1543,12 +1607,10 @@ cifs_get_tcp_session(struct smb_vol *volume_info) cFYI(1, "attempting ipv6 connect"); /* BB should we allow ipv6 on port 139? */ /* other OS never observed in Wild doing 139 with v6 */ - sin_server6->sin6_port = htons(volume_info->port); memcpy(&tcp_ses->addr.sockAddr6, sin_server6, sizeof(struct sockaddr_in6)); rc = ipv6_connect(tcp_ses); } else { - sin_server->sin_port = htons(volume_info->port); memcpy(&tcp_ses->addr.sockAddr, sin_server, sizeof(struct sockaddr_in)); rc = ipv4_connect(tcp_ses); @@ -1577,6 +1639,8 @@ cifs_get_tcp_session(struct smb_vol *volume_info) list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list); write_unlock(&cifs_tcp_ses_lock); + cifs_fscache_get_client_cookie(tcp_ses); + return tcp_ses; out_err: @@ -1591,17 +1655,27 @@ out_err: } static struct cifsSesInfo * -cifs_find_smb_ses(struct TCP_Server_Info *server, char *username) +cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol) { - struct list_head *tmp; struct cifsSesInfo *ses; write_lock(&cifs_tcp_ses_lock); - list_for_each(tmp, &server->smb_ses_list) { - ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list); - if (strncmp(ses->userName, username, MAX_USERNAME_SIZE)) - continue; - + list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { + switch (server->secType) { + case Kerberos: + if (vol->cred_uid != ses->cred_uid) + continue; + break; + default: + /* anything else takes username/password */ + if (strncmp(ses->userName, vol->username, + MAX_USERNAME_SIZE)) + continue; + if (strlen(vol->username) != 0 && + strncmp(ses->password, vol->password, + MAX_PASSWORD_SIZE)) + continue; + } ++ses->ses_count; write_unlock(&cifs_tcp_ses_lock); return ses; @@ -1643,7 +1717,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info) xid = GetXid(); - ses = cifs_find_smb_ses(server, volume_info->username); + ses = cifs_find_smb_ses(server, volume_info); if (ses) { cFYI(1, "Existing smb sess found (status=%d)", ses->status); @@ -1706,6 +1780,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info) if (ses->domainName) strcpy(ses->domainName, volume_info->domainname); } + ses->cred_uid = volume_info->cred_uid; ses->linux_uid = volume_info->linux_uid; ses->overrideSecFlg = volume_info->secFlg; @@ -1773,6 +1848,7 @@ cifs_put_tcon(struct cifsTconInfo *tcon) CIFSSMBTDis(xid, tcon); _FreeXid(xid); + cifs_fscache_release_super_cookie(tcon); tconInfoFree(tcon); cifs_put_smb_ses(ses); } @@ -1843,6 +1919,8 @@ cifs_get_tcon(struct cifsSesInfo *ses, struct smb_vol *volume_info) list_add(&tcon->tcon_list, &ses->tcon_list); write_unlock(&cifs_tcp_ses_lock); + cifs_fscache_get_super_cookie(tcon); + return tcon; out_fail: @@ -2397,6 +2475,8 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info, cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_GID; if (pvolume_info->dynperm) cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DYNPERM; + if (pvolume_info->fsc) + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_FSCACHE; if (pvolume_info->direct_io) { cFYI(1, "mounting share using direct i/o"); cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO; diff --git a/fs/cifs/dir.c b/fs/cifs/dir.c index e7ae78b66fa..a7de5e9fff1 100644 --- a/fs/cifs/dir.c +++ b/fs/cifs/dir.c @@ -130,12 +130,6 @@ cifs_bp_rename_retry: return full_path; } -/* - * When called with struct file pointer set to NULL, there is no way we could - * update file->private_data, but getting it stuck on openFileList provides a - * way to access it from cifs_fill_filedata and thereby set file->private_data - * from cifs_open. - */ struct cifsFileInfo * cifs_new_fileinfo(struct inode *newinode, __u16 fileHandle, struct file *file, struct vfsmount *mnt, unsigned int oflags) diff --git a/fs/cifs/dns_resolve.c b/fs/cifs/dns_resolve.c index 853a968e82d..3ad7f4300c4 100644 --- a/fs/cifs/dns_resolve.c +++ b/fs/cifs/dns_resolve.c @@ -44,7 +44,7 @@ is_ip(char *name) { struct sockaddr_storage ss; - return cifs_convert_address(name, &ss); + return cifs_convert_address((struct sockaddr *)&ss, name); } static int diff --git a/fs/cifs/file.c b/fs/cifs/file.c index 409e4f523e6..fa04a00d126 100644 --- a/fs/cifs/file.c +++ b/fs/cifs/file.c @@ -40,6 +40,7 @@ #include "cifs_unicode.h" #include "cifs_debug.h" #include "cifs_fs_sb.h" +#include "fscache.h" static inline int cifs_convert_flags(unsigned int flags) { @@ -282,6 +283,9 @@ int cifs_open(struct inode *inode, struct file *file) CIFSSMBClose(xid, tcon, netfid); rc = -ENOMEM; } + + cifs_fscache_set_inode_cookie(inode, file); + goto out; } else if ((rc == -EINVAL) || (rc == -EOPNOTSUPP)) { if (tcon->ses->serverNOS) @@ -373,6 +377,8 @@ int cifs_open(struct inode *inode, struct file *file) goto out; } + cifs_fscache_set_inode_cookie(inode, file); + if (oplock & CIFS_CREATE_ACTION) { /* time to set mode which we can not set earlier due to problems creating new read-only files */ @@ -427,7 +433,7 @@ static int cifs_reopen_file(struct file *file, bool can_flush) __u16 netfid; if (file->private_data) - pCifsFile = (struct cifsFileInfo *)file->private_data; + pCifsFile = file->private_data; else return -EBADF; @@ -565,8 +571,7 @@ int cifs_close(struct inode *inode, struct file *file) int xid, timeout; struct cifs_sb_info *cifs_sb; struct cifsTconInfo *pTcon; - struct cifsFileInfo *pSMBFile = - (struct cifsFileInfo *)file->private_data; + struct cifsFileInfo *pSMBFile = file->private_data; xid = GetXid(); @@ -641,8 +646,7 @@ int cifs_closedir(struct inode *inode, struct file *file) { int rc = 0; int xid; - struct cifsFileInfo *pCFileStruct = - (struct cifsFileInfo *)file->private_data; + struct cifsFileInfo *pCFileStruct = file->private_data; char *ptmp; cFYI(1, "Closedir inode = 0x%p", inode); @@ -863,8 +867,7 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock) length, pfLock, posix_lock_type, wait_flag); } else { - struct cifsFileInfo *fid = - (struct cifsFileInfo *)file->private_data; + struct cifsFileInfo *fid = file->private_data; if (numLock) { rc = CIFSSMBLock(xid, tcon, netfid, length, @@ -965,7 +968,7 @@ ssize_t cifs_user_write(struct file *file, const char __user *write_data, if (file->private_data == NULL) return -EBADF; - open_file = (struct cifsFileInfo *) file->private_data; + open_file = file->private_data; rc = generic_write_checks(file, poffset, &write_size, 0); if (rc) @@ -1067,7 +1070,7 @@ static ssize_t cifs_write(struct file *file, const char *write_data, if (file->private_data == NULL) return -EBADF; - open_file = (struct cifsFileInfo *)file->private_data; + open_file = file->private_data; xid = GetXid(); @@ -1651,8 +1654,7 @@ int cifs_fsync(struct file *file, int datasync) int xid; int rc = 0; struct cifsTconInfo *tcon; - struct cifsFileInfo *smbfile = - (struct cifsFileInfo *)file->private_data; + struct cifsFileInfo *smbfile = file->private_data; struct inode *inode = file->f_path.dentry->d_inode; xid = GetXid(); @@ -1756,7 +1758,7 @@ ssize_t cifs_user_read(struct file *file, char __user *read_data, FreeXid(xid); return rc; } - open_file = (struct cifsFileInfo *)file->private_data; + open_file = file->private_data; if ((file->f_flags & O_ACCMODE) == O_WRONLY) cFYI(1, "attempting read on write only file instance"); @@ -1837,7 +1839,7 @@ static ssize_t cifs_read(struct file *file, char *read_data, size_t read_size, FreeXid(xid); return rc; } - open_file = (struct cifsFileInfo *)file->private_data; + open_file = file->private_data; if ((file->f_flags & O_ACCMODE) == O_WRONLY) cFYI(1, "attempting read on write only file instance"); @@ -1942,6 +1944,9 @@ static void cifs_copy_cache_pages(struct address_space *mapping, SetPageUptodate(page); unlock_page(page); data += PAGE_CACHE_SIZE; + + /* add page to FS-Cache */ + cifs_readpage_to_fscache(mapping->host, page); } return; } @@ -1968,10 +1973,19 @@ static int cifs_readpages(struct file *file, struct address_space *mapping, FreeXid(xid); return rc; } - open_file = (struct cifsFileInfo *)file->private_data; + open_file = file->private_data; cifs_sb = CIFS_SB(file->f_path.dentry->d_sb); pTcon = cifs_sb->tcon; + /* + * Reads as many pages as possible from fscache. Returns -ENOBUFS + * immediately if the cookie is negative + */ + rc = cifs_readpages_from_fscache(mapping->host, mapping, page_list, + &num_pages); + if (rc == 0) + goto read_complete; + cFYI(DBG2, "rpages: num pages %d", num_pages); for (i = 0; i < num_pages; ) { unsigned contig_pages; @@ -2082,6 +2096,7 @@ static int cifs_readpages(struct file *file, struct address_space *mapping, smb_read_data = NULL; } +read_complete: FreeXid(xid); return rc; } @@ -2092,6 +2107,11 @@ static int cifs_readpage_worker(struct file *file, struct page *page, char *read_data; int rc; + /* Is the page cached? */ + rc = cifs_readpage_from_fscache(file->f_path.dentry->d_inode, page); + if (rc == 0) + goto read_complete; + page_cache_get(page); read_data = kmap(page); /* for reads over a certain size could initiate async read ahead */ @@ -2111,11 +2131,17 @@ static int cifs_readpage_worker(struct file *file, struct page *page, flush_dcache_page(page); SetPageUptodate(page); + + /* send this page to the cache */ + cifs_readpage_to_fscache(file->f_path.dentry->d_inode, page); + rc = 0; io_error: kunmap(page); page_cache_release(page); + +read_complete: return rc; } @@ -2265,6 +2291,22 @@ out: return rc; } +static int cifs_release_page(struct page *page, gfp_t gfp) +{ + if (PagePrivate(page)) + return 0; + + return cifs_fscache_release_page(page, gfp); +} + +static void cifs_invalidate_page(struct page *page, unsigned long offset) +{ + struct cifsInodeInfo *cifsi = CIFS_I(page->mapping->host); + + if (offset == 0) + cifs_fscache_invalidate_page(page, &cifsi->vfs_inode); +} + static void cifs_oplock_break(struct slow_work *work) { @@ -2338,6 +2380,8 @@ const struct address_space_operations cifs_addr_ops = { .write_begin = cifs_write_begin, .write_end = cifs_write_end, .set_page_dirty = __set_page_dirty_nobuffers, + .releasepage = cifs_release_page, + .invalidatepage = cifs_invalidate_page, /* .sync_page = cifs_sync_page, */ /* .direct_IO = */ }; @@ -2354,6 +2398,8 @@ const struct address_space_operations cifs_addr_ops_smallbuf = { .write_begin = cifs_write_begin, .write_end = cifs_write_end, .set_page_dirty = __set_page_dirty_nobuffers, + .releasepage = cifs_release_page, + .invalidatepage = cifs_invalidate_page, /* .sync_page = cifs_sync_page, */ /* .direct_IO = */ }; diff --git a/fs/cifs/fscache.c b/fs/cifs/fscache.c new file mode 100644 index 00000000000..9f3f5c4be16 --- /dev/null +++ b/fs/cifs/fscache.c @@ -0,0 +1,236 @@ +/* + * fs/cifs/fscache.c - CIFS filesystem cache interface + * + * Copyright (c) 2010 Novell, Inc. + * Author(s): Suresh Jayaraman (sjayaraman@suse.de> + * + * 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 "fscache.h" +#include "cifsglob.h" +#include "cifs_debug.h" +#include "cifs_fs_sb.h" + +void cifs_fscache_get_client_cookie(struct TCP_Server_Info *server) +{ + server->fscache = + fscache_acquire_cookie(cifs_fscache_netfs.primary_index, + &cifs_fscache_server_index_def, server); + cFYI(1, "CIFS: get client cookie (0x%p/0x%p)", server, + server->fscache); +} + +void cifs_fscache_release_client_cookie(struct TCP_Server_Info *server) +{ + cFYI(1, "CIFS: release client cookie (0x%p/0x%p)", server, + server->fscache); + fscache_relinquish_cookie(server->fscache, 0); + server->fscache = NULL; +} + +void cifs_fscache_get_super_cookie(struct cifsTconInfo *tcon) +{ + struct TCP_Server_Info *server = tcon->ses->server; + + tcon->fscache = + fscache_acquire_cookie(server->fscache, + &cifs_fscache_super_index_def, tcon); + cFYI(1, "CIFS: get superblock cookie (0x%p/0x%p)", + server->fscache, tcon->fscache); +} + +void cifs_fscache_release_super_cookie(struct cifsTconInfo *tcon) +{ + cFYI(1, "CIFS: releasing superblock cookie (0x%p)", tcon->fscache); + fscache_relinquish_cookie(tcon->fscache, 0); + tcon->fscache = NULL; +} + +static void cifs_fscache_enable_inode_cookie(struct inode *inode) +{ + struct cifsInodeInfo *cifsi = CIFS_I(inode); + struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); + + if (cifsi->fscache) + return; + + cifsi->fscache = fscache_acquire_cookie(cifs_sb->tcon->fscache, + &cifs_fscache_inode_object_def, + cifsi); + cFYI(1, "CIFS: got FH cookie (0x%p/0x%p)", + cifs_sb->tcon->fscache, cifsi->fscache); +} + +void cifs_fscache_release_inode_cookie(struct inode *inode) +{ + struct cifsInodeInfo *cifsi = CIFS_I(inode); + + if (cifsi->fscache) { + cFYI(1, "CIFS releasing inode cookie (0x%p)", + cifsi->fscache); + fscache_relinquish_cookie(cifsi->fscache, 0); + cifsi->fscache = NULL; + } +} + +static void cifs_fscache_disable_inode_cookie(struct inode *inode) +{ + struct cifsInodeInfo *cifsi = CIFS_I(inode); + + if (cifsi->fscache) { + cFYI(1, "CIFS disabling inode cookie (0x%p)", + cifsi->fscache); + fscache_relinquish_cookie(cifsi->fscache, 1); + cifsi->fscache = NULL; + } +} + +void cifs_fscache_set_inode_cookie(struct inode *inode, struct file *filp) +{ + if ((filp->f_flags & O_ACCMODE) != O_RDONLY) + cifs_fscache_disable_inode_cookie(inode); + else { + cifs_fscache_enable_inode_cookie(inode); + cFYI(1, "CIFS: fscache inode cookie set"); + } +} + +void cifs_fscache_reset_inode_cookie(struct inode *inode) +{ + struct cifsInodeInfo *cifsi = CIFS_I(inode); + struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); + struct fscache_cookie *old = cifsi->fscache; + + if (cifsi->fscache) { + /* retire the current fscache cache and get a new one */ + fscache_relinquish_cookie(cifsi->fscache, 1); + + cifsi->fscache = fscache_acquire_cookie(cifs_sb->tcon->fscache, + &cifs_fscache_inode_object_def, + cifsi); + cFYI(1, "CIFS: new cookie 0x%p oldcookie 0x%p", + cifsi->fscache, old); + } +} + +int cifs_fscache_release_page(struct page *page, gfp_t gfp) +{ + if (PageFsCache(page)) { + struct inode *inode = page->mapping->host; + struct cifsInodeInfo *cifsi = CIFS_I(inode); + + cFYI(1, "CIFS: fscache release page (0x%p/0x%p)", + page, cifsi->fscache); + if (!fscache_maybe_release_page(cifsi->fscache, page, gfp)) + return 0; + } + + return 1; +} + +static void cifs_readpage_from_fscache_complete(struct page *page, void *ctx, + int error) +{ + cFYI(1, "CFS: readpage_from_fscache_complete (0x%p/%d)", + page, error); + if (!error) + SetPageUptodate(page); + unlock_page(page); +} + +/* + * Retrieve a page from FS-Cache + */ +int __cifs_readpage_from_fscache(struct inode *inode, struct page *page) +{ + int ret; + + cFYI(1, "CIFS: readpage_from_fscache(fsc:%p, p:%p, i:0x%p", + CIFS_I(inode)->fscache, page, inode); + ret = fscache_read_or_alloc_page(CIFS_I(inode)->fscache, page, + cifs_readpage_from_fscache_complete, + NULL, + GFP_KERNEL); + switch (ret) { + + case 0: /* page found in fscache, read submitted */ + cFYI(1, "CIFS: readpage_from_fscache: submitted"); + return ret; + case -ENOBUFS: /* page won't be cached */ + case -ENODATA: /* page not in cache */ + cFYI(1, "CIFS: readpage_from_fscache %d", ret); + return 1; + + default: + cERROR(1, "unknown error ret = %d", ret); + } + return ret; +} + +/* + * Retrieve a set of pages from FS-Cache + */ +int __cifs_readpages_from_fscache(struct inode *inode, + struct address_space *mapping, + struct list_head *pages, + unsigned *nr_pages) +{ + int ret; + + cFYI(1, "CIFS: __cifs_readpages_from_fscache (0x%p/%u/0x%p)", + CIFS_I(inode)->fscache, *nr_pages, inode); + ret = fscache_read_or_alloc_pages(CIFS_I(inode)->fscache, mapping, + pages, nr_pages, + cifs_readpage_from_fscache_complete, + NULL, + mapping_gfp_mask(mapping)); + switch (ret) { + case 0: /* read submitted to the cache for all pages */ + cFYI(1, "CIFS: readpages_from_fscache: submitted"); + return ret; + + case -ENOBUFS: /* some pages are not cached and can't be */ + case -ENODATA: /* some pages are not cached */ + cFYI(1, "CIFS: readpages_from_fscache: no page"); + return 1; + + default: + cFYI(1, "unknown error ret = %d", ret); + } + + return ret; +} + +void __cifs_readpage_to_fscache(struct inode *inode, struct page *page) +{ + int ret; + + cFYI(1, "CIFS: readpage_to_fscache(fsc: %p, p: %p, i: %p", + CIFS_I(inode)->fscache, page, inode); + ret = fscache_write_page(CIFS_I(inode)->fscache, page, GFP_KERNEL); + if (ret != 0) + fscache_uncache_page(CIFS_I(inode)->fscache, page); +} + +void __cifs_fscache_invalidate_page(struct page *page, struct inode *inode) +{ + struct cifsInodeInfo *cifsi = CIFS_I(inode); + struct fscache_cookie *cookie = cifsi->fscache; + + cFYI(1, "CIFS: fscache invalidatepage (0x%p/0x%p)", page, cookie); + fscache_wait_on_page_write(cookie, page); + fscache_uncache_page(cookie, page); +} + diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h new file mode 100644 index 00000000000..31b88ec2341 --- /dev/null +++ b/fs/cifs/fscache.h @@ -0,0 +1,136 @@ +/* + * fs/cifs/fscache.h - CIFS filesystem cache interface definitions + * + * Copyright (c) 2010 Novell, Inc. + * Authors(s): Suresh Jayaraman (sjayaraman@suse.de> + * + * 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 + */ +#ifndef _CIFS_FSCACHE_H +#define _CIFS_FSCACHE_H + +#include <linux/fscache.h> + +#include "cifsglob.h" + +#ifdef CONFIG_CIFS_FSCACHE + +extern struct fscache_netfs cifs_fscache_netfs; +extern const struct fscache_cookie_def cifs_fscache_server_index_def; +extern const struct fscache_cookie_def cifs_fscache_super_index_def; +extern const struct fscache_cookie_def cifs_fscache_inode_object_def; + +extern int cifs_fscache_register(void); +extern void cifs_fscache_unregister(void); + +/* + * fscache.c + */ +extern void cifs_fscache_get_client_cookie(struct TCP_Server_Info *); +extern void cifs_fscache_release_client_cookie(struct TCP_Server_Info *); +extern void cifs_fscache_get_super_cookie(struct cifsTconInfo *); +extern void cifs_fscache_release_super_cookie(struct cifsTconInfo *); + +extern void cifs_fscache_release_inode_cookie(struct inode *); +extern void cifs_fscache_set_inode_cookie(struct inode *, struct file *); +extern void cifs_fscache_reset_inode_cookie(struct inode *); + +extern void __cifs_fscache_invalidate_page(struct page *, struct inode *); +extern int cifs_fscache_release_page(struct page *page, gfp_t gfp); +extern int __cifs_readpage_from_fscache(struct inode *, struct page *); +extern int __cifs_readpages_from_fscache(struct inode *, + struct address_space *, + struct list_head *, + unsigned *); + +extern void __cifs_readpage_to_fscache(struct inode *, struct page *); + +static inline void cifs_fscache_invalidate_page(struct page *page, + struct inode *inode) +{ + if (PageFsCache(page)) + __cifs_fscache_invalidate_page(page, inode); +} + +static inline int cifs_readpage_from_fscache(struct inode *inode, + struct page *page) +{ + if (CIFS_I(inode)->fscache) + return __cifs_readpage_from_fscache(inode, page); + + return -ENOBUFS; +} + +static inline int cifs_readpages_from_fscache(struct inode *inode, + struct address_space *mapping, + struct list_head *pages, + unsigned *nr_pages) +{ + if (CIFS_I(inode)->fscache) + return __cifs_readpages_from_fscache(inode, mapping, pages, + nr_pages); + return -ENOBUFS; +} + +static inline void cifs_readpage_to_fscache(struct inode *inode, + struct page *page) +{ + if (PageFsCache(page)) + __cifs_readpage_to_fscache(inode, page); +} + +#else /* CONFIG_CIFS_FSCACHE */ +static inline int cifs_fscache_register(void) { return 0; } +static inline void cifs_fscache_unregister(void) {} + +static inline void +cifs_fscache_get_client_cookie(struct TCP_Server_Info *server) {} +static inline void +cifs_fscache_release_client_cookie(struct TCP_Server_Info *server) {} +static inline void cifs_fscache_get_super_cookie(struct cifsTconInfo *tcon) {} +static inline void +cifs_fscache_release_super_cookie(struct cifsTconInfo *tcon) {} + +static inline void cifs_fscache_release_inode_cookie(struct inode *inode) {} +static inline void cifs_fscache_set_inode_cookie(struct inode *inode, + struct file *filp) {} +static inline void cifs_fscache_reset_inode_cookie(struct inode *inode) {} +static inline int cifs_fscache_release_page(struct page *page, gfp_t gfp) +{ + return 1; /* May release page */ +} + +static inline void cifs_fscache_invalidate_page(struct page *page, + struct inode *inode) {} +static inline int +cifs_readpage_from_fscache(struct inode *inode, struct page *page) +{ + return -ENOBUFS; +} + +static inline int cifs_readpages_from_fscache(struct inode *inode, + struct address_space *mapping, + struct list_head *pages, + unsigned *nr_pages) +{ + return -ENOBUFS; +} + +static inline void cifs_readpage_to_fscache(struct inode *inode, + struct page *page) {} + +#endif /* CONFIG_CIFS_FSCACHE */ + +#endif /* _CIFS_FSCACHE_H */ diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index 6f0683c6895..a15b3a9bbff 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -29,6 +29,7 @@ #include "cifsproto.h" #include "cifs_debug.h" #include "cifs_fs_sb.h" +#include "fscache.h" static void cifs_set_ops(struct inode *inode, const bool is_dfs_referral) @@ -288,7 +289,7 @@ int cifs_get_file_info_unix(struct file *filp) struct inode *inode = filp->f_path.dentry->d_inode; struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct cifsTconInfo *tcon = cifs_sb->tcon; - struct cifsFileInfo *cfile = (struct cifsFileInfo *) filp->private_data; + struct cifsFileInfo *cfile = filp->private_data; xid = GetXid(); rc = CIFSSMBUnixQFileInfo(xid, tcon, cfile->netfid, &find_data); @@ -515,7 +516,7 @@ int cifs_get_file_info(struct file *filp) struct inode *inode = filp->f_path.dentry->d_inode; struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct cifsTconInfo *tcon = cifs_sb->tcon; - struct cifsFileInfo *cfile = (struct cifsFileInfo *) filp->private_data; + struct cifsFileInfo *cfile = filp->private_data; xid = GetXid(); rc = CIFSSMBQFileInfo(xid, tcon, cfile->netfid, &find_data); @@ -723,9 +724,14 @@ cifs_find_inode(struct inode *inode, void *opaque) { struct cifs_fattr *fattr = (struct cifs_fattr *) opaque; + /* don't match inode with different uniqueid */ if (CIFS_I(inode)->uniqueid != fattr->cf_uniqueid) return 0; + /* don't match inode of different type */ + if ((inode->i_mode & S_IFMT) != (fattr->cf_mode & S_IFMT)) + return 0; + /* * uh oh -- it's a directory. We can't use it since hardlinked dirs are * verboten. Disable serverino and return it as if it were found, the @@ -776,6 +782,10 @@ retry_iget5_locked: inode->i_flags |= S_NOATIME | S_NOCMTIME; if (inode->i_state & I_NEW) { inode->i_ino = hash; +#ifdef CONFIG_CIFS_FSCACHE + /* initialize per-inode cache cookie pointer */ + CIFS_I(inode)->fscache = NULL; +#endif unlock_new_inode(inode); } } @@ -807,6 +817,11 @@ struct inode *cifs_root_iget(struct super_block *sb, unsigned long ino) if (!inode) return ERR_PTR(-ENOMEM); +#ifdef CONFIG_CIFS_FSCACHE + /* populate tcon->resource_id */ + cifs_sb->tcon->resource_id = CIFS_I(inode)->uniqueid; +#endif + if (rc && cifs_sb->tcon->ipc) { cFYI(1, "ipc connection - fake read inode"); inode->i_mode |= S_IFDIR; @@ -1568,6 +1583,7 @@ cifs_invalidate_mapping(struct inode *inode) cifs_i->write_behind_rc = rc; } invalidate_remote_inode(inode); + cifs_fscache_reset_inode_cookie(inode); } int cifs_revalidate_file(struct file *filp) diff --git a/fs/cifs/ioctl.c b/fs/cifs/ioctl.c index 505926f1ee6..9d38a71c8e1 100644 --- a/fs/cifs/ioctl.c +++ b/fs/cifs/ioctl.c @@ -41,8 +41,7 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) __u64 ExtAttrMask = 0; __u64 caps; struct cifsTconInfo *tcon; - struct cifsFileInfo *pSMBFile = - (struct cifsFileInfo *)filep->private_data; + struct cifsFileInfo *pSMBFile = filep->private_data; #endif /* CONFIG_CIFS_POSIX */ xid = GetXid(); diff --git a/fs/cifs/netmisc.c b/fs/cifs/netmisc.c index d35d52889cb..c6721ee26db 100644 --- a/fs/cifs/netmisc.c +++ b/fs/cifs/netmisc.c @@ -61,6 +61,7 @@ static const struct smb_to_posix_error mapping_table_ERRDOS[] = { {ERRremcd, -EACCES}, {ERRdiffdevice, -EXDEV}, {ERRnofiles, -ENOENT}, + {ERRwriteprot, -EROFS}, {ERRbadshare, -ETXTBSY}, {ERRlock, -EACCES}, {ERRunsup, -EINVAL}, @@ -164,7 +165,7 @@ cifs_inet_pton(const int address_family, const char *cp, void *dst) * Returns 0 on failure. */ int -cifs_convert_address(char *src, void *dst) +cifs_convert_address(struct sockaddr *dst, char *src) { int rc; char *pct, *endp; @@ -201,6 +202,27 @@ cifs_convert_address(char *src, void *dst) return rc; } +int +cifs_fill_sockaddr(struct sockaddr *dst, char *src, + const unsigned short int port) +{ + if (!cifs_convert_address(dst, src)) + return 0; + + switch (dst->sa_family) { + case AF_INET: + ((struct sockaddr_in *)dst)->sin_port = htons(port); + break; + case AF_INET6: + ((struct sockaddr_in6 *)dst)->sin6_port = htons(port); + break; + default: + return 0; + } + + return 1; +} + /***************************************************************************** convert a NT status code to a dos class/code *****************************************************************************/ diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c index daf1753af67..d5e591fab47 100644 --- a/fs/cifs/readdir.c +++ b/fs/cifs/readdir.c @@ -847,6 +847,11 @@ int cifs_readdir(struct file *file, void *direntry, filldir_t filldir) end_of_smb = cifsFile->srch_inf.ntwrk_buf_start + max_len; tmp_buf = kmalloc(UNICODE_NAME_MAX, GFP_KERNEL); + if (tmp_buf == NULL) { + rc = -ENOMEM; + break; + } + for (i = 0; (i < num_to_fill) && (rc == 0); i++) { if (current_entry == NULL) { /* evaluate whether this case is an error */ diff --git a/fs/cifs/smberr.h b/fs/cifs/smberr.h index c5084d27db7..7f16cb825fe 100644 --- a/fs/cifs/smberr.h +++ b/fs/cifs/smberr.h @@ -76,6 +76,7 @@ #define ERRnofiles 18 /* A File Search command can find no more files matching the specified criteria. */ +#define ERRwriteprot 19 /* media is write protected */ #define ERRgeneral 31 #define ERRbadshare 32 /* The sharing mode specified for an Open conflicts with existing FIDs on |