Hi Al, Christoph, Trond, Stephen, Casey,
Here's a set of patches that implement a very basic set of COW credentials. It
compiles, links and runs for x86_64 with EXT3, (V)FAT, NFS, AFS, SELinux and
keyrings all enabled. Most other filesystems are disabled, apart from things
like proc. It is not intended to completely cover the kernel at this point.
The cred struct contains the credentials that the kernel needs to act upon
something or to create something. Credentials that govern how a task may be
acted upon remain in the task struct.
In essence, the introduction of the cred struct separates a task's subjective
context (the authority with which it acts) from its objective context (the
authorisation required by others that want to act upon it), and permits
overriding of the subjective context by a kernel service so that the service
can act on the task's behalf to do something the task couldn't do on its own
authority.
Because keyrings and effective capabilities can be installed or changed in one
process by another process, they are shadowed by the cred structure rather than
residing there. Additionally, the session and process keyrings are shared
between all the threads of a process. The shadowing is performed by
update_current_cred() which is invoked on entry to any system call that might
need it.
A thread's cred struct may be read by that thread without any RCU precautions
as only that thread may replace the its own cred struct. To change a thread's
credentials, dup_cred() should be called to create a new copy, the copy should
be changed, and then set_current_cred() should be called to make it live. Once
live, it may not be changed as it may then be shared with file descriptors, RPC
calls and other threads. RCU will be used to dispose of the old structure.
The four patches are:
(1) Introduce struct cred and migrate fsuid, fsgid, the groups list and the
keyrings pointer to it.
(2) Introduce a security pointer into the cred struct and add LSM hooks to
duplicate the information pointed to thereby and to free it.
Make SELinux implement the hooks, splitting out some the task security
data to be associated with struct cred instead.
(3) Migrate the effective capabilities mask into the cred struct.
(4) Provide a pair of LSM hooks so that a kernel service can (a) get a
credential record representing the authority with which it is permitted to
act, and (b) alter the file creation context in a credential record.
In addition, as this works with cachefiles, I've included all the FS-Cache,
CacheFiles, NFS and AFS patches.
To substitute a temporary set of credentials, the cred struct attached to the
task should be altered, like so:
int get_privileged_creds(...)
{
/* get special privileged creds */
my_special_cred = get_kernel_cred("cachefiles", current);
change_create_files_as(my_special_cred, my_cache_dir);
}
int do_stuff(...)
{
struct cred *cred;
/* rotate in the new creds, saving the old */
cred = __set_current_cred(get_cred(my_special_cred));
do_privileged_stuff();
/* restore the old creds */
set_current_cred(cred);
}
One thing I'm not certain about is how this should interact with /proc, which
can display some of the stuff in the cred struct. I think it may be necessary
to have a real cred pointer and an effective cred pointer, with the contents of
/proc coming from the real, but the effective governing what actually goes on.
David
Display the local caching state in /proc/fs/nfsfs/volumes.
Signed-off-by: David Howells <[email protected]>
---
fs/nfs/client.c | 7 ++++---
fs/nfs/fscache.h | 12 ++++++++++++
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index 0de4db4..d350668 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -1319,7 +1319,7 @@ static int nfs_volume_list_show(struct seq_file *m, void *v)
/* display header on line 1 */
if (v == &nfs_volume_list) {
- seq_puts(m, "NV SERVER PORT DEV FSID\n");
+ seq_puts(m, "NV SERVER PORT DEV FSID FSC\n");
return 0;
}
/* display one transport per line on subsequent lines */
@@ -1333,12 +1333,13 @@ static int nfs_volume_list_show(struct seq_file *m, void *v)
(unsigned long long) server->fsid.major,
(unsigned long long) server->fsid.minor);
- seq_printf(m, "v%d %02x%02x%02x%02x %4hx %-7s %-17s\n",
+ seq_printf(m, "v%d %02x%02x%02x%02x %4hx %-7s %-17s %s\n",
clp->cl_nfsversion,
NIPQUAD(clp->cl_addr.sin_addr),
ntohs(clp->cl_addr.sin_port),
dev,
- fsid);
+ fsid,
+ nfs_server_fscache_state(server));
return 0;
}
diff --git a/fs/nfs/fscache.h b/fs/nfs/fscache.h
index 44bb0d1..77f3450 100644
--- a/fs/nfs/fscache.h
+++ b/fs/nfs/fscache.h
@@ -56,6 +56,17 @@ extern void __nfs_fscache_invalidate_page(struct page *, struct inode *);
extern int nfs_fscache_release_page(struct page *, gfp_t);
/*
+ * indicate the client caching state as readable text
+ */
+static inline const char *nfs_server_fscache_state(struct nfs_server *server)
+{
+ if (server->nfs_client->fscache &&
+ (server->options & NFS_OPTION_FSCACHE))
+ return "yes";
+ return "no ";
+}
+
+/*
* release the caching state associated with a page if undergoing complete page
* invalidation
*/
@@ -110,6 +121,7 @@ static inline void nfs_fscache_unregister(void) {}
static inline void nfs_fscache_get_client_cookie(struct nfs_client *clp) {}
static inline void nfs4_fscache_get_client_cookie(struct nfs_client *clp) {}
static inline void nfs_fscache_release_client_cookie(struct nfs_client *clp) {}
+static inline const char *nfs_server_fscache_state(struct nfs_server *server) { return "no "; }
static inline void nfs_fscache_init_fh_cookie(struct inode *inode) {}
static inline void nfs_fscache_enable_fh_cookie(struct inode *inode) {}
Export a number of functions for CacheFiles's use.
Signed-Off-By: David Howells <[email protected]>
---
fs/super.c | 2 ++
kernel/auditsc.c | 2 ++
2 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/fs/super.c b/fs/super.c
index 28e7370..0e8c0e2 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -270,6 +270,8 @@ int fsync_super(struct super_block *sb)
return sync_blockdev(sb->s_bdev);
}
+EXPORT_SYMBOL_GPL(fsync_super);
+
/**
* generic_shutdown_super - common helper for ->kill_sb()
* @sb: superblock to kill
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 282e041..4448a33 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -1531,6 +1531,8 @@ add_names:
}
}
+EXPORT_SYMBOL_GPL(__audit_inode_child);
+
/**
* auditsc_get_stamp - get local copies of audit_context values
* @ctx: audit_context for the task
Changes to the kernel configuration defintions and to the NFS mount options to
allow the local caching support added by the previous patch to be enabled.
Signed-off-by: David Howells <[email protected]>
---
fs/Kconfig | 8 ++++++++
fs/nfs/client.c | 14 ++++++++++----
fs/nfs/internal.h | 2 ++
fs/nfs/super.c | 40 ++++++++++++++++++++++++++++++++++------
4 files changed, 54 insertions(+), 10 deletions(-)
diff --git a/fs/Kconfig b/fs/Kconfig
index 8ae7eda..ebc7341 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -1597,6 +1597,14 @@ config NFS_V4
If unsure, say N.
+config NFS_FSCACHE
+ bool "Provide NFS client caching support (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ depends on NFS_FS=m && FSCACHE || NFS_FS=y && FSCACHE=y
+ help
+ Say Y here if you want NFS data to be cached locally on disc through
+ the general filesystem cache manager
+
config NFS_DIRECTIO
bool "Allow direct I/O on NFS files"
depends on NFS_FS
diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index f1783b2..0de4db4 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -543,7 +543,8 @@ error:
/*
* Create a version 2 or 3 client
*/
-static int nfs_init_server(struct nfs_server *server, const struct nfs_mount_data *data)
+static int nfs_init_server(struct nfs_server *server, const struct nfs_mount_data *data,
+ unsigned int extra_options)
{
struct nfs_client *clp;
int error, nfsvers = 2;
@@ -580,6 +581,7 @@ static int nfs_init_server(struct nfs_server *server, const struct nfs_mount_dat
server->acregmax = data->acregmax * HZ;
server->acdirmin = data->acdirmin * HZ;
server->acdirmax = data->acdirmax * HZ;
+ server->options = extra_options;
/* Start lockd here, before we might error out */
error = nfs_start_lockd(server);
@@ -776,6 +778,7 @@ void nfs_free_server(struct nfs_server *server)
* - keyed on server and FSID
*/
struct nfs_server *nfs_create_server(const struct nfs_mount_data *data,
+ unsigned extra_options,
struct nfs_fh *mntfh)
{
struct nfs_server *server;
@@ -787,7 +790,7 @@ struct nfs_server *nfs_create_server(const struct nfs_mount_data *data,
return ERR_PTR(-ENOMEM);
/* Get a client representation */
- error = nfs_init_server(server, data);
+ error = nfs_init_server(server, data, extra_options);
if (error < 0)
goto error;
@@ -911,7 +914,8 @@ error:
* Create a version 4 volume record
*/
static int nfs4_init_server(struct nfs_server *server,
- const struct nfs4_mount_data *data, rpc_authflavor_t authflavour)
+ const struct nfs4_mount_data *data, rpc_authflavor_t authflavour,
+ unsigned int extra_options)
{
int error;
@@ -930,6 +934,7 @@ static int nfs4_init_server(struct nfs_server *server,
server->acregmax = data->acregmax * HZ;
server->acdirmin = data->acdirmin * HZ;
server->acdirmax = data->acdirmax * HZ;
+ server->options = extra_options;
error = nfs_init_server_rpcclient(server, authflavour);
@@ -948,6 +953,7 @@ struct nfs_server *nfs4_create_server(const struct nfs4_mount_data *data,
const char *mntpath,
const char *ip_addr,
rpc_authflavor_t authflavour,
+ unsigned int extra_options,
struct nfs_fh *mntfh)
{
struct nfs_fattr fattr;
@@ -967,7 +973,7 @@ struct nfs_server *nfs4_create_server(const struct nfs4_mount_data *data,
goto error;
/* set up the general RPC client */
- error = nfs4_init_server(server, data, authflavour);
+ error = nfs4_init_server(server, data, authflavour, extra_options);
if (error < 0)
goto error;
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 76cf55d..34ef000 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -33,6 +33,7 @@ extern struct rpc_program nfs_program;
extern void nfs_put_client(struct nfs_client *);
extern struct nfs_client *nfs_find_client(const struct sockaddr_in *, int);
extern struct nfs_server *nfs_create_server(const struct nfs_mount_data *,
+ unsigned int,
struct nfs_fh *);
extern struct nfs_server *nfs4_create_server(const struct nfs4_mount_data *,
const char *,
@@ -40,6 +41,7 @@ extern struct nfs_server *nfs4_create_server(const struct nfs4_mount_data *,
const char *,
const char *,
rpc_authflavor_t,
+ unsigned int,
struct nfs_fh *);
extern struct nfs_server *nfs4_create_referral_server(struct nfs_clone_mount *,
struct nfs_fh *);
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index b878528..7753e9e 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -67,6 +67,7 @@ struct nfs_parsed_mount_data {
acdirmin, acdirmax;
int namlen;
unsigned int bsize;
+ unsigned int options;
unsigned int auth_flavor_len;
rpc_authflavor_t auth_flavors[1];
char *client_address;
@@ -101,6 +102,7 @@ enum {
Opt_acl, Opt_noacl,
Opt_rdirplus, Opt_nordirplus,
Opt_sharecache, Opt_nosharecache,
+ Opt_fscache, Opt_nofscache,
/* Mount options that take integer arguments */
Opt_port,
@@ -149,6 +151,8 @@ static match_table_t nfs_mount_option_tokens = {
{ Opt_nordirplus, "nordirplus" },
{ Opt_sharecache, "sharecache" },
{ Opt_nosharecache, "nosharecache" },
+ { Opt_fscache, "fsc" },
+ { Opt_nofscache, "nofsc" },
{ Opt_port, "port=%u" },
{ Opt_rsize, "rsize=%u" },
@@ -495,6 +499,8 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss,
seq_printf(m, ",timeo=%lu", 10U * clp->retrans_timeo / HZ);
seq_printf(m, ",retrans=%u", clp->retrans_count);
seq_printf(m, ",sec=%s", nfs_pseudoflavour_to_name(nfss->client->cl_auth->au_flavor));
+ if (nfss->options & NFS_OPTION_FSCACHE)
+ seq_printf(m, ",fsc");
}
/*
@@ -725,6 +731,15 @@ static int nfs_parse_mount_options(char *raw,
break;
case Opt_nosharecache:
mnt->flags |= NFS_MOUNT_UNSHARED;
+ mnt->options &= ~NFS_OPTION_FSCACHE;
+ break;
+ case Opt_fscache:
+ /* sharing is mandatory with fscache */
+ mnt->options |= NFS_OPTION_FSCACHE;
+ mnt->flags &= ~NFS_MOUNT_UNSHARED;
+ break;
+ case Opt_nofscache:
+ mnt->options &= ~NFS_OPTION_FSCACHE;
break;
case Opt_port:
@@ -1081,10 +1096,13 @@ out_err:
*/
static int nfs_validate_mount_data(struct nfs_mount_data **options,
struct nfs_fh *mntfh,
- const char *dev_name)
+ const char *dev_name,
+ unsigned int *_extra_options)
{
struct nfs_mount_data *data = *options;
+ *_extra_options = 0;
+
if (data == NULL)
goto out_no_data;
@@ -1131,6 +1149,7 @@ static int nfs_validate_mount_data(struct nfs_mount_data **options,
.acregmax = 60,
.acdirmin = 30,
.acdirmax = 60,
+ .options = 0,
.mount_server.protocol = IPPROTO_UDP,
.mount_server.program = NFS_MNT_PROGRAM,
.nfs_server.protocol = IPPROTO_TCP,
@@ -1139,6 +1158,7 @@ static int nfs_validate_mount_data(struct nfs_mount_data **options,
if (nfs_parse_mount_options((char *) *options, &args) == 0)
return -EINVAL;
+ *_extra_options = args.options;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (data == NULL)
@@ -1381,6 +1401,7 @@ static int nfs_get_sb(struct file_system_type *fs_type,
struct nfs_fh mntfh;
struct nfs_mount_data *data = raw_data;
struct dentry *mntroot;
+ unsigned int extra_options;
int (*compare_super)(struct super_block *, void *) = nfs_compare_super;
struct nfs_sb_mountdata sb_mntdata = {
.mntflags = flags,
@@ -1388,12 +1409,12 @@ static int nfs_get_sb(struct file_system_type *fs_type,
int error;
/* Validate the mount data */
- error = nfs_validate_mount_data(&data, &mntfh, dev_name);
+ error = nfs_validate_mount_data(&data, &mntfh, dev_name, &extra_options);
if (error < 0)
goto out;
/* Get a volume representation */
- server = nfs_create_server(data, &mntfh);
+ server = nfs_create_server(data, extra_options, &mntfh);
if (IS_ERR(server)) {
error = PTR_ERR(server);
goto out;
@@ -1565,11 +1586,14 @@ static int nfs4_validate_mount_data(struct nfs4_mount_data **options,
rpc_authflavor_t *authflavour,
char **hostname,
char **mntpath,
- char **ip_addr)
+ char **ip_addr,
+ unsigned int *_extra_options)
{
struct nfs4_mount_data *data = *options;
char *c;
+ *_extra_options = 0;
+
if (data == NULL)
goto out_no_data;
@@ -1625,11 +1649,13 @@ static int nfs4_validate_mount_data(struct nfs4_mount_data **options,
.acregmax = 60,
.acdirmin = 30,
.acdirmax = 60,
+ .options = 0,
.nfs_server.protocol = IPPROTO_TCP,
};
if (nfs_parse_mount_options((char *) *options, &args) == 0)
return -EINVAL;
+ *_extra_options = args.options;
if (!nfs_verify_server_address((struct sockaddr *)
&args.nfs_server.address))
@@ -1736,6 +1762,7 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
rpc_authflavor_t authflavour;
struct nfs_fh mntfh;
struct dentry *mntroot;
+ unsigned extra_options;
char *mntpath = NULL, *hostname = NULL, *ip_addr = NULL;
int (*compare_super)(struct super_block *, void *) = nfs_compare_super;
struct nfs_sb_mountdata sb_mntdata = {
@@ -1745,13 +1772,14 @@ static int nfs4_get_sb(struct file_system_type *fs_type,
/* Validate the mount data */
error = nfs4_validate_mount_data(&data, dev_name, &addr, &authflavour,
- &hostname, &mntpath, &ip_addr);
+ &hostname, &mntpath, &ip_addr,
+ &extra_options);
if (error < 0)
goto out;
/* Get a volume representation */
server = nfs4_create_server(data, hostname, &addr, mntpath, ip_addr,
- authflavour, &mntfh);
+ authflavour, extra_options, &mntfh);
if (IS_ERR(server)) {
error = PTR_ERR(server);
goto out;
Add a TestSetPageError() macro to the suite of page flag manipulators. This
can be used by AFS to prevent over-excision of rejected writes from the page
cache.
Signed-off-by: David Howells <[email protected]>
---
include/linux/page-flags.h | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index eaf9854..b59506b 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -130,6 +130,7 @@
#define PageError(page) test_bit(PG_error, &(page)->flags)
#define SetPageError(page) set_bit(PG_error, &(page)->flags)
#define ClearPageError(page) clear_bit(PG_error, &(page)->flags)
+#define TestSetPageError(page) test_and_set_bit(PG_error, &(page)->flags)
#define PageReferenced(page) test_bit(PG_referenced, &(page)->flags)
#define SetPageReferenced(page) set_bit(PG_referenced, &(page)->flags)
The attached patch makes it possible for the NFS filesystem to make use of the
network filesystem local caching service (FS-Cache).
To be able to use this, an updated mount program is required. This can be
obtained from:
http://people.redhat.com/steved/fscache/util-linux/
To mount an NFS filesystem to use caching, add an "fsc" option to the mount:
mount warthog:/ /a -o fsc
Signed-Off-By: David Howells <[email protected]>
---
fs/nfs/Makefile | 1
fs/nfs/client.c | 5 +
fs/nfs/file.c | 51 ++++++
fs/nfs/fscache-def.c | 288 +++++++++++++++++++++++++++++++++++
fs/nfs/fscache.c | 372 +++++++++++++++++++++++++++++++++++++++++++++
fs/nfs/fscache.h | 144 +++++++++++++++++
fs/nfs/inode.c | 48 +++++-
fs/nfs/read.c | 28 +++
fs/nfs/sysctl.c | 44 +++++
include/linux/nfs_fs.h | 8 +
include/linux/nfs_fs_sb.h | 7 +
11 files changed, 986 insertions(+), 10 deletions(-)
diff --git a/fs/nfs/Makefile b/fs/nfs/Makefile
index b55cb23..07c9345 100644
--- a/fs/nfs/Makefile
+++ b/fs/nfs/Makefile
@@ -16,4 +16,5 @@ nfs-$(CONFIG_NFS_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4renewd.o \
nfs4namespace.o
nfs-$(CONFIG_NFS_DIRECTIO) += direct.o
nfs-$(CONFIG_SYSCTL) += sysctl.o
+nfs-$(CONFIG_NFS_FSCACHE) += fscache.o fscache-def.o
nfs-objs := $(nfs-y)
diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index a49f9fe..f1783b2 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -41,6 +41,7 @@
#include "delegation.h"
#include "iostat.h"
#include "internal.h"
+#include "fscache.h"
#define NFSDBG_FACILITY NFSDBG_CLIENT
@@ -137,6 +138,8 @@ static struct nfs_client *nfs_alloc_client(const char *hostname,
clp->cl_state = 1 << NFS4CLNT_LEASE_EXPIRED;
#endif
+ nfs_fscache_get_client_cookie(clp);
+
return clp;
error_3:
@@ -168,6 +171,8 @@ static void nfs_free_client(struct nfs_client *clp)
nfs4_shutdown_client(clp);
+ nfs_fscache_release_client_cookie(clp);
+
/* -EIO all pending I/O */
if (!IS_ERR(clp->cl_rpcclient))
rpc_shutdown_client(clp->cl_rpcclient);
diff --git a/fs/nfs/file.c b/fs/nfs/file.c
index 579cf8a..640179b 100644
--- a/fs/nfs/file.c
+++ b/fs/nfs/file.c
@@ -34,6 +34,8 @@
#include "delegation.h"
#include "iostat.h"
+#include "internal.h"
+#include "fscache.h"
#define NFSDBG_FACILITY NFSDBG_FILE
@@ -54,6 +56,12 @@ static int nfs_check_flags(int flags);
static int nfs_lock(struct file *filp, int cmd, struct file_lock *fl);
static int nfs_flock(struct file *filp, int cmd, struct file_lock *fl);
static int nfs_setlease(struct file *file, long arg, struct file_lock **fl);
+static int nfs_file_page_mkwrite(struct vm_area_struct *vma, struct page *page);
+
+struct vm_operations_struct nfs_fs_vm_operations = {
+ .fault = filemap_fault,
+ .page_mkwrite = nfs_file_page_mkwrite,
+};
const struct file_operations nfs_file_operations = {
.llseek = nfs_file_llseek,
@@ -259,6 +267,9 @@ nfs_file_mmap(struct file * file, struct vm_area_struct * vma)
status = nfs_revalidate_mapping(inode, file->f_mapping);
if (!status)
status = generic_file_mmap(file, vma);
+ if (!status)
+ vma->vm_ops = &nfs_fs_vm_operations;
+
return status;
}
@@ -311,22 +322,48 @@ static int nfs_commit_write(struct file *file, struct page *page, unsigned offse
return status;
}
+/*
+ * Partially or wholly invalidate a page
+ * - Release the private state associated with a page if undergoing complete
+ * page invalidation
+ * - Called if either PG_private or PG_fscache set on the page
+ * - Caller holds page lock
+ */
static void nfs_invalidate_page(struct page *page, unsigned long offset)
{
if (offset != 0)
return;
/* Cancel any unstarted writes on this page */
nfs_wb_page_cancel(page->mapping->host, page);
+
+ nfs_fscache_invalidate_page(page, page->mapping->host);
}
+/*
+ * Release the private state associated with a page
+ * - Called if either PG_private or PG_fscache set on the page
+ * - Caller holds page lock
+ * - Return true (may release) or false (may not)
+ */
static int nfs_release_page(struct page *page, gfp_t gfp)
{
/* If PagePrivate() is set, then the page is not freeable */
- return 0;
+ if (PagePrivate(page))
+ return 0;
+ return nfs_fscache_release_page(page, gfp);
}
+/*
+ * Attempt to clear the private state associated with a page when an error
+ * occurs that requires the cached contents of an inode to be written back or
+ * destroyed
+ * - Called if either PG_private or PG_fscache set on the page
+ * - Caller holds page lock
+ * - Return 0 if successful, -error otherwise
+ */
static int nfs_launder_page(struct page *page)
{
+ wait_on_page_fscache_write(page);
return nfs_wb_page(page->mapping->host, page);
}
@@ -572,3 +609,15 @@ static int nfs_setlease(struct file *file, long arg, struct file_lock **fl)
*/
return -EINVAL;
}
+
+/*
+ * Notification that a PTE pointing to an NFS page is about to be made
+ * writable, implying that someone is about to modify the page through a
+ * shared-writable mapping
+ */
+static int nfs_file_page_mkwrite(struct vm_area_struct *vma, struct page *page)
+{
+ /* make sure the cache has finished storing the page */
+ wait_on_page_fscache_write(page);
+ return 0;
+}
diff --git a/fs/nfs/fscache-def.c b/fs/nfs/fscache-def.c
new file mode 100644
index 0000000..4aa2bab
--- /dev/null
+++ b/fs/nfs/fscache-def.c
@@ -0,0 +1,288 @@
+/* NFS FS-Cache index structure definition
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/nfs_fs.h>
+#include <linux/nfs_fs_sb.h>
+#include <linux/in6.h>
+
+#include "internal.h"
+#include "fscache.h"
+
+#define NFSDBG_FACILITY NFSDBG_FSCACHE
+
+/*
+ * Definition of the auxiliary data attached to NFS inode storage objects.
+ * This is used for coherency management.
+ */
+struct nfs_fh_auxdata {
+ struct timespec i_mtime;
+ struct timespec i_ctime;
+ loff_t i_size;
+};
+
+/*
+ * Definition of the key for an NFS server index object. The server's IP
+ * address is stored as an IPv6 address, with IPv4 addresses being wrapped
+ * appropriately.
+ */
+struct nfs_server_key {
+ uint16_t nfsversion;
+ uint16_t port;
+ union {
+ struct {
+ uint8_t ipv6wrapper[12];
+ struct in_addr addr;
+ } ipv4_addr;
+ struct in6_addr ipv6_addr;
+ };
+};
+
+static const struct fscache_netfs_operations nfs_cache_ops = {
+};
+
+struct fscache_netfs nfs_cache_netfs = {
+ .name = "nfs",
+ .version = 0,
+ .ops = &nfs_cache_ops,
+};
+
+static const uint8_t nfs_cache_ipv6_wrapper_for_ipv4[12] = {
+ [0 ... 9] = 0x00,
+ [10 ... 11] = 0xff
+};
+
+/*
+ * Generate a key to describe a server in the main NFS index
+ */
+static uint16_t nfs_server_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
+{
+ const struct nfs_client *clp = cookie_netfs_data;
+ struct nfs_server_key *key = buffer;
+ uint16_t len = 0;
+
+ key->nfsversion = clp->cl_nfsversion;
+
+ switch (clp->cl_addr.sin_family) {
+ case AF_INET:
+ key->port = clp->cl_addr.sin_port;
+
+ memcpy(&key->ipv4_addr.ipv6wrapper,
+ &nfs_cache_ipv6_wrapper_for_ipv4,
+ sizeof(key->ipv4_addr.ipv6wrapper));
+ memcpy(&key->ipv4_addr.addr,
+ &clp->cl_addr.sin_addr,
+ sizeof(key->ipv4_addr.addr));
+ len = sizeof(struct nfs_server_key);
+ break;
+
+ case AF_INET6:
+ key->port = clp->cl_addr.sin_port;
+
+ memcpy(&key->ipv6_addr,
+ &clp->cl_addr.sin_addr,
+ sizeof(key->ipv6_addr));
+ len = sizeof(struct nfs_server_key);
+ break;
+
+ default:
+ len = 0;
+ printk(KERN_WARNING "NFS: Unknown network family '%d'\n",
+ clp->cl_addr.sin_family);
+ break;
+ }
+
+ return len;
+}
+
+/*
+ * The root index for the filesystem is defined by nfsd IP address and ports
+ */
+const struct fscache_cookie_def nfs_cache_server_index_def = {
+ .name = "NFS.servers",
+ .type = FSCACHE_COOKIE_TYPE_INDEX,
+ .get_key = nfs_server_get_key,
+};
+
+/*
+ * Generate a key to describe an NFS inode in an NFS server's index
+ */
+static uint16_t nfs_fh_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
+{
+ const struct nfs_inode *nfsi = cookie_netfs_data;
+ uint16_t nsize;
+
+ /* use the inode's NFS filehandle as the key */
+ nsize = nfsi->fh.size;
+ memcpy(buffer, nfsi->fh.data, nsize);
+ return nsize;
+}
+
+/*
+ * Get an extra reference on a read context
+ * - This function can be absent if the completion function doesn't require a
+ * context
+ */
+static void nfs_fh_get_context(void *cookie_netfs_data, void *context)
+{
+ get_nfs_open_context(context);
+}
+
+/*
+ * Release an extra reference on a read context
+ * - This function can be absent if the completion function doesn't require a
+ * context
+ */
+static void nfs_fh_put_context(void *cookie_netfs_data, void *context)
+{
+ if (context)
+ put_nfs_open_context(context);
+}
+
+/*
+ * Indication the cookie is no longer uncached
+ * - This function is called when the backing store currently caching a cookie
+ * is removed
+ * - The netfs should use this to clean up any markers indicating cached pages
+ * - This is mandatory for any object that may have data
+ */
+static void nfs_fh_now_uncached(void *cookie_netfs_data)
+{
+ struct nfs_inode *nfsi = cookie_netfs_data;
+ struct pagevec pvec;
+ pgoff_t first;
+ int loop, nr_pages;
+
+ pagevec_init(&pvec, 0);
+ first = 0;
+
+ dprintk("NFS: nfs_fh_now_uncached: nfs_inode 0x%p\n", nfsi);
+
+ for (;;) {
+ /* grab a bunch of pages to clean */
+ nr_pages = pagevec_lookup(&pvec,
+ nfsi->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();
+ }
+}
+
+/*
+ * Tet certain file attributes from the netfs data
+ * - This function can be absent for an index
+ * - Not permitted to return an error
+ * - The netfs data from the cookie being used as the source is
+ * presented
+ */
+static void nfs_fh_get_attr(const void *cookie_netfs_data, uint64_t *size)
+{
+ const struct nfs_inode *nfsi = cookie_netfs_data;
+
+ *size = nfsi->vfs_inode.i_size;
+}
+
+/*
+ * Get the auxiliary data from netfs data
+ * - This function can be absent if the index carries no state data
+ * - Should store the auxiliary data in the buffer
+ * - Should return the amount of amount stored
+ * - Not permitted to return an error
+ * - The netfs data from the cookie being used as the source is presented
+ */
+static uint16_t nfs_fh_get_aux(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
+{
+ struct nfs_fh_auxdata auxdata;
+ const struct nfs_inode *nfsi = cookie_netfs_data;
+
+ auxdata.i_size = nfsi->vfs_inode.i_size;
+ auxdata.i_mtime = nfsi->vfs_inode.i_mtime;
+ auxdata.i_ctime = nfsi->vfs_inode.i_ctime;
+
+ if (bufmax > sizeof(auxdata))
+ bufmax = sizeof(auxdata);
+
+ memcpy(buffer, &auxdata, bufmax);
+ return bufmax;
+}
+
+/*
+ * Consult the netfs about the state of an object
+ * - This function can be absent if the index carries no state data
+ * - The netfs data from the cookie being used as the target is
+ * presented, as is the auxiliary data
+ */
+static fscache_checkaux_t nfs_fh_check_aux(void *cookie_netfs_data,
+ const void *data, uint16_t datalen)
+{
+ struct nfs_fh_auxdata auxdata;
+ struct nfs_inode *nfsi = cookie_netfs_data;
+
+ if (datalen > sizeof(auxdata))
+ return FSCACHE_CHECKAUX_OBSOLETE;
+
+ auxdata.i_size = nfsi->vfs_inode.i_size;
+ auxdata.i_mtime = nfsi->vfs_inode.i_mtime;
+ auxdata.i_ctime = nfsi->vfs_inode.i_ctime;
+
+ if (memcmp(data, &auxdata, datalen) != 0)
+ return FSCACHE_CHECKAUX_OBSOLETE;
+
+ return FSCACHE_CHECKAUX_OKAY;
+}
+
+/*
+ * The primary index for each server is simply made up of a series of NFS file
+ * handles
+ */
+const struct fscache_cookie_def nfs_cache_fh_index_def = {
+ .name = "NFS.fh",
+ .type = FSCACHE_COOKIE_TYPE_DATAFILE,
+ .get_key = nfs_fh_get_key,
+ .get_attr = nfs_fh_get_attr,
+ .get_aux = nfs_fh_get_aux,
+ .check_aux = nfs_fh_check_aux,
+ .get_context = nfs_fh_get_context,
+ .put_context = nfs_fh_put_context,
+ .now_uncached = nfs_fh_now_uncached,
+};
+
+/*
+ * Register NFS for caching
+ */
+int nfs_fscache_register(void)
+{
+ return fscache_register_netfs(&nfs_cache_netfs);
+}
+
+/*
+ * Unregister NFS for caching
+ */
+void nfs_fscache_unregister(void)
+{
+ fscache_unregister_netfs(&nfs_cache_netfs);
+}
diff --git a/fs/nfs/fscache.c b/fs/nfs/fscache.c
new file mode 100644
index 0000000..3d122b6
--- /dev/null
+++ b/fs/nfs/fscache.c
@@ -0,0 +1,372 @@
+/* NFS filesystem cache interface
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/nfs_fs.h>
+#include <linux/nfs_fs_sb.h>
+#include <linux/in6.h>
+
+#include "internal.h"
+#include "fscache.h"
+
+#define NFSDBG_FACILITY NFSDBG_FSCACHE
+
+/*
+ * Sysctl variables
+ */
+atomic_t nfs_fscache_to_pages;
+atomic_t nfs_fscache_from_pages;
+atomic_t nfs_fscache_uncache_page;
+int nfs_fscache_from_error;
+int nfs_fscache_to_error;
+
+/*
+ * Get the per-client index cookie for an NFS client if the appropriate mount
+ * flag was set
+ * - We always try and get an index cookie for the client, but get filehandle
+ * cookies on a per-superblock basis, depending on the mount flags
+ */
+void nfs_fscache_get_client_cookie(struct nfs_client *clp)
+{
+ /* create a cache index for looking up filehandles */
+ clp->fscache = fscache_acquire_cookie(nfs_cache_netfs.primary_index,
+ &nfs_cache_server_index_def,
+ clp);
+ dfprintk(FSCACHE,"NFS: get client cookie (0x%p/0x%p)\n",
+ clp, clp->fscache);
+}
+
+/*
+ * Dispose of a per-client cookie
+ */
+void nfs_fscache_release_client_cookie(struct nfs_client *clp)
+{
+ dfprintk(FSCACHE, "NFS: releasing client cookie (0x%p/0x%p)\n",
+ clp, clp->fscache);
+
+ fscache_relinquish_cookie(clp->fscache, 0);
+ clp->fscache = NULL;
+}
+
+/*
+ * Initialise the per-filehandle cookie for an NFS inode
+ */
+void nfs_fscache_init_fh_cookie(struct inode *inode)
+{
+ NFS_I(inode)->fscache = NULL;
+ if (S_ISREG(inode->i_mode))
+ set_bit(NFS_INO_FSCACHE, &NFS_I(inode)->flags);
+}
+
+/*
+ * Get the per-filehandle cookie for an NFS inode
+ */
+void nfs_fscache_enable_fh_cookie(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+ struct nfs_inode *nfsi = NFS_I(inode);
+
+ if (nfsi->fscache || !NFS_FSCACHE(inode))
+ return;
+
+ if ((NFS_SB(sb)->options & NFS_OPTION_FSCACHE)) {
+ nfsi->fscache = fscache_acquire_cookie(
+ NFS_SB(sb)->nfs_client->fscache,
+ &nfs_cache_fh_index_def,
+ nfsi);
+
+ dfprintk(FSCACHE, "NFS: get FH cookie (0x%p/0x%p/0x%p)\n",
+ sb, nfsi, nfsi->fscache);
+ }
+}
+
+/*
+ * Release a per-filehandle cookie
+ */
+void nfs_fscache_release_fh_cookie(struct inode *inode)
+{
+ struct nfs_inode *nfsi = NFS_I(inode);
+
+ dfprintk(FSCACHE, "NFS: clear cookie (0x%p/0x%p)\n",
+ nfsi, nfsi->fscache);
+
+ fscache_relinquish_cookie(nfsi->fscache, 0);
+ nfsi->fscache = NULL;
+}
+
+/*
+ * Retire a per-filehandle cookie, destroying the data attached to it
+ */
+void nfs_fscache_zap_fh_cookie(struct inode *inode)
+{
+ struct nfs_inode *nfsi = NFS_I(inode);
+
+ dfprintk(FSCACHE,"NFS: zapping cookie (0x%p/0x%p)\n",
+ nfsi, nfsi->fscache);
+
+ fscache_relinquish_cookie(nfsi->fscache, 1);
+ nfsi->fscache = NULL;
+}
+
+/*
+ * Turn off the cache with regard to a filehandle cookie if opened for writing,
+ * invalidating all the pages in the page cache relating to the associated
+ * inode to clear the per-page caching
+ */
+void nfs_fscache_disable_fh_cookie(struct inode *inode)
+{
+ clear_bit(NFS_INO_FSCACHE, &NFS_I(inode)->flags);
+
+ if (NFS_I(inode)->fscache) {
+ dfprintk(FSCACHE,
+ "NFS: nfsi 0x%p turning cache off\n", NFS_I(inode));
+
+ /* Need to invalidate any mapped pages that were read in before
+ * turning off the cache.
+ */
+ if (inode->i_mapping && inode->i_mapping->nrpages)
+ invalidate_inode_pages2(inode->i_mapping);
+
+ nfs_fscache_zap_fh_cookie(inode);
+ }
+}
+
+/*
+ * Decide if we should enable or disable the FS cache for this inode
+ * - For now, only regular files that are open read-only will be able to use
+ * the cache
+ */
+void nfs_fscache_set_fh_cookie(struct inode *inode, struct file *filp)
+{
+ if (NFS_FSCACHE(inode)) {
+ if ((filp->f_flags & O_ACCMODE) != O_RDONLY)
+ nfs_fscache_disable_fh_cookie(inode);
+ else
+ nfs_fscache_enable_fh_cookie(inode);
+ }
+}
+
+/*
+ * Replace a per-filehandle cookie due to revalidation detecting a file having
+ * changed on the server
+ */
+void nfs_fscache_renew_fh_cookie(struct inode *inode)
+{
+ struct nfs_inode *nfsi = NFS_I(inode);
+ struct nfs_server *server = NFS_SERVER(inode);
+ struct fscache_cookie *old = nfsi->fscache;
+
+ if (nfsi->fscache) {
+ /* retire the current fscache cache and get a new one */
+ fscache_relinquish_cookie(nfsi->fscache, 1);
+
+ nfsi->fscache = fscache_acquire_cookie(
+ server->nfs_client->fscache,
+ &nfs_cache_fh_index_def,
+ nfsi);
+
+ dfprintk(FSCACHE,
+ "NFS: revalidation new cookie (0x%p/0x%p/0x%p/0x%p)\n",
+ server, nfsi, old, nfsi->fscache);
+ }
+}
+
+/*
+ * change the filesize associated with a per-filehandle cookie
+ */
+void nfs_fscache_attr_changed(struct inode *inode)
+{
+ fscache_attr_changed(NFS_I(inode)->fscache);
+}
+
+/*
+ * Handle completion of a page being read from the cache
+ * - Called in process (keventd) context
+ */
+static void nfs_readpage_from_fscache_complete(struct page *page,
+ void *context,
+ int error)
+{
+ dfprintk(FSCACHE,
+ "NFS: readpage_from_fscache_complete (0x%p/0x%p/%d)\n",
+ page, context, error);
+
+ /* if the read completes with an error, we just unlock the page and let
+ * the VM reissue the readpage */
+ if (!error) {
+ SetPageUptodate(page);
+ unlock_page(page);
+ } else {
+ error = nfs_readpage_async(context, page->mapping->host, page);
+ if (error)
+ unlock_page(page);
+ }
+}
+
+/*
+ * Store a newly fetched page in fscache
+ * - PG_fscache must be set on the page
+ */
+void __nfs_readpage_to_fscache(struct inode *inode, struct page *page, int sync)
+{
+ int ret;
+
+ dfprintk(FSCACHE,
+ "NFS: readpage_to_fscache(fsc:%p/p:%p(i:%lx f:%lx)/%d)\n",
+ NFS_I(inode)->fscache, page, page->index, page->flags, sync);
+
+ ret = fscache_write_page(NFS_I(inode)->fscache, page, GFP_KERNEL);
+ dfprintk(FSCACHE,
+ "NFS: readpage_to_fscache: p:%p(i:%lu f:%lx) ret %d\n",
+ page, page->index, page->flags, ret);
+
+ if (ret != 0) {
+ fscache_uncache_page(NFS_I(inode)->fscache, page);
+ atomic_inc(&nfs_fscache_uncache_page);
+ nfs_fscache_to_error = ret;
+ } else {
+ atomic_inc(&nfs_fscache_to_pages);
+ }
+}
+
+/*
+ * Retrieve a page from fscache
+ */
+int __nfs_readpage_from_fscache(struct nfs_open_context *ctx,
+ struct inode *inode, struct page *page)
+{
+ int ret;
+
+ dfprintk(FSCACHE,
+ "NFS: readpage_from_fscache(fsc:%p/p:%p(i:%lx f:%lx)/0x%p)\n",
+ NFS_I(inode)->fscache, page, page->index, page->flags, inode);
+
+ ret = fscache_read_or_alloc_page(NFS_I(inode)->fscache,
+ page,
+ nfs_readpage_from_fscache_complete,
+ ctx,
+ GFP_KERNEL);
+
+ switch (ret) {
+ case 0: /* read BIO submitted (page in fscache) */
+ dfprintk(FSCACHE,
+ "NFS: readpage_from_fscache: BIO submitted\n");
+ atomic_inc(&nfs_fscache_from_pages);
+ return ret;
+
+ case -ENOBUFS: /* inode not in cache */
+ case -ENODATA: /* page not in cache */
+ dfprintk(FSCACHE,
+ "NFS: readpage_from_fscache error %d\n", ret);
+ return 1;
+
+ default:
+ dfprintk(FSCACHE, "NFS: readpage_from_fscache %d\n", ret);
+ nfs_fscache_from_error = ret;
+ }
+ return ret;
+}
+
+/*
+ * Retrieve a set of pages from fscache
+ */
+int __nfs_readpages_from_fscache(struct nfs_open_context *ctx,
+ struct inode *inode,
+ struct address_space *mapping,
+ struct list_head *pages,
+ unsigned *nr_pages)
+{
+ int ret, npages = *nr_pages;
+
+ dfprintk(FSCACHE, "NFS: nfs_getpages_from_fscache (0x%p/%u/0x%p)\n",
+ NFS_I(inode)->fscache, npages, inode);
+
+ ret = fscache_read_or_alloc_pages(NFS_I(inode)->fscache,
+ mapping, pages, nr_pages,
+ nfs_readpage_from_fscache_complete,
+ ctx,
+ mapping_gfp_mask(mapping));
+
+
+ switch (ret) {
+ case 0: /* read BIO submitted (page in fscache) */
+ BUG_ON(!list_empty(pages));
+ BUG_ON(*nr_pages != 0);
+ dfprintk(FSCACHE, "NFS: nfs_getpages_from_fscache: submitted\n");
+
+ atomic_add(npages, &nfs_fscache_from_pages);
+ return ret;
+
+ case -ENOBUFS: /* inode not in cache */
+ case -ENODATA: /* page not in cache */
+ dfprintk(FSCACHE,
+ "NFS: nfs_getpages_from_fscache: no page: %d\n", ret);
+ return 1;
+
+ default:
+ dfprintk(FSCACHE,
+ "NFS: nfs_getpages_from_fscache: ret %d\n", ret);
+ nfs_fscache_from_error = ret;
+ }
+
+ return ret;
+}
+
+/*
+ * Release the caching state associated with a page, if the page isn't busy
+ * interacting with the cache
+ * - Returns true (can release page) or false (page busy)
+ */
+int nfs_fscache_release_page(struct page *page, gfp_t gfp)
+{
+ if (PageFsCacheWrite(page)) {
+ if (!(gfp & __GFP_WAIT))
+ return 0;
+ wait_on_page_fscache_write(page);
+ }
+
+ if (PageFsCache(page)) {
+ struct nfs_inode *nfsi = NFS_I(page->mapping->host);
+
+ BUG_ON(!nfsi->fscache);
+
+ dfprintk(FSCACHE, "NFS: fscache releasepage (0x%p/0x%p/0x%p)\n",
+ nfsi->fscache, page, nfsi);
+
+ fscache_uncache_page(nfsi->fscache, page);
+ atomic_inc(&nfs_fscache_uncache_page);
+ }
+
+ return 1;
+}
+
+/*
+ * Release the caching state associated with a page if undergoing complete page
+ * invalidation
+ */
+void __nfs_fscache_invalidate_page(struct page *page, struct inode *inode)
+{
+ struct nfs_inode *nfsi = NFS_I(page->mapping->host);
+
+ BUG_ON(!nfsi->fscache);
+
+ dfprintk(FSCACHE, "NFS: fscache invalidatepage (0x%p/0x%p/0x%p)\n",
+ nfsi->fscache, page, nfsi);
+
+ wait_on_page_fscache_write(page);
+
+ BUG_ON(!PageLocked(page));
+ fscache_uncache_page(nfsi->fscache, page);
+ atomic_inc(&nfs_fscache_uncache_page);
+}
diff --git a/fs/nfs/fscache.h b/fs/nfs/fscache.h
new file mode 100644
index 0000000..44bb0d1
--- /dev/null
+++ b/fs/nfs/fscache.h
@@ -0,0 +1,144 @@
+/* NFS filesystem cache interface definitions
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _NFS_FSCACHE_H
+#define _NFS_FSCACHE_H
+
+#include <linux/nfs_fs.h>
+#include <linux/nfs_mount.h>
+#include <linux/nfs4_mount.h>
+
+#ifdef CONFIG_NFS_FSCACHE
+#include <linux/fscache.h>
+
+/*
+ * fscache-def.c
+ */
+extern struct fscache_netfs nfs_cache_netfs;
+extern const struct fscache_cookie_def nfs_cache_server_index_def;
+extern const struct fscache_cookie_def nfs_cache_fh_index_def;
+
+extern int nfs_fscache_register(void);
+extern void nfs_fscache_unregister(void);
+
+/*
+ * fscache.c
+ */
+extern atomic_t nfs_fscache_to_pages;
+extern atomic_t nfs_fscache_from_pages;
+extern atomic_t nfs_fscache_uncache_page;
+extern int nfs_fscache_from_error;
+extern int nfs_fscache_to_error;
+
+extern void nfs_fscache_get_client_cookie(struct nfs_client *);
+extern void nfs_fscache_release_client_cookie(struct nfs_client *);
+extern void nfs_fscache_init_fh_cookie(struct inode *);
+extern void nfs_fscache_enable_fh_cookie(struct inode *);
+extern void nfs_fscache_release_fh_cookie(struct inode *);
+extern void nfs_fscache_zap_fh_cookie(struct inode *);
+extern void nfs_fscache_disable_fh_cookie(struct inode *);
+extern void nfs_fscache_set_fh_cookie(struct inode *, struct file *);
+extern void nfs_fscache_renew_fh_cookie(struct inode *);
+extern void nfs_fscache_attr_changed(struct inode *);
+extern void __nfs_readpage_to_fscache(struct inode *, struct page *, int);
+extern int __nfs_readpage_from_fscache(struct nfs_open_context *, struct inode *, struct page *);
+extern int __nfs_readpages_from_fscache(struct nfs_open_context *, struct inode *,
+ struct address_space *, struct list_head *, unsigned *);
+extern void __nfs_fscache_invalidate_page(struct page *, struct inode *);
+extern int nfs_fscache_release_page(struct page *, gfp_t);
+
+/*
+ * release the caching state associated with a page if undergoing complete page
+ * invalidation
+ */
+static inline void nfs_fscache_invalidate_page(struct page *page,
+ struct inode *inode)
+{
+ if (PageFsCache(page))
+ __nfs_fscache_invalidate_page(page, inode);
+}
+
+/*
+ * store a newly fetched page in fscache
+ */
+static inline void nfs_readpage_to_fscache(struct inode *inode,
+ struct page *page,
+ int sync)
+{
+ if (PageFsCache(page))
+ __nfs_readpage_to_fscache(inode, page, sync);
+}
+
+/*
+ * retrieve a page from fscache
+ */
+static inline int nfs_readpage_from_fscache(struct nfs_open_context *ctx,
+ struct inode *inode,
+ struct page *page)
+{
+ if (NFS_I(inode)->fscache)
+ return __nfs_readpage_from_fscache(ctx, inode, page);
+ return -ENOBUFS;
+}
+
+/*
+ * retrieve a set of pages from fscache
+ */
+static inline int nfs_readpages_from_fscache(struct nfs_open_context *ctx,
+ struct inode *inode,
+ struct address_space *mapping,
+ struct list_head *pages,
+ unsigned *nr_pages)
+{
+ if (NFS_I(inode)->fscache)
+ return __nfs_readpages_from_fscache(ctx, inode, mapping, pages,
+ nr_pages);
+ return -ENOBUFS;
+}
+
+#else /* CONFIG_NFS_FSCACHE */
+static inline int nfs_fscache_register(void) { return 0; }
+static inline void nfs_fscache_unregister(void) {}
+static inline void nfs_fscache_get_client_cookie(struct nfs_client *clp) {}
+static inline void nfs4_fscache_get_client_cookie(struct nfs_client *clp) {}
+static inline void nfs_fscache_release_client_cookie(struct nfs_client *clp) {}
+
+static inline void nfs_fscache_init_fh_cookie(struct inode *inode) {}
+static inline void nfs_fscache_enable_fh_cookie(struct inode *inode) {}
+static inline void nfs_fscache_attr_changed(struct inode *inode) {}
+static inline void nfs_fscache_release_fh_cookie(struct inode *inode) {}
+static inline void nfs_fscache_zap_fh_cookie(struct inode *inode) {}
+static inline void nfs_fscache_renew_fh_cookie(struct inode *inode) {}
+static inline void nfs_fscache_disable_fh_cookie(struct inode *inode) {}
+static inline void nfs_fscache_set_fh_cookie(struct inode *inode, struct file *filp) {}
+static inline void nfs_fscache_install_vm_ops(struct inode *inode, struct vm_area_struct *vma) {}
+static inline int nfs_fscache_release_page(struct page *page, gfp_t gfp)
+{
+ return 1; /* True: may release page */
+}
+static inline void nfs_fscache_invalidate_page(struct page *page, struct inode *inode) {}
+static inline void nfs_readpage_to_fscache(struct inode *inode, struct page *page, int sync) {}
+static inline int nfs_readpage_from_fscache(struct nfs_open_context *ctx,
+ struct inode *inode, struct page *page)
+{
+ return -ENOBUFS;
+}
+static inline int nfs_readpages_from_fscache(struct nfs_open_context *ctx,
+ struct inode *inode,
+ struct address_space *mapping,
+ struct list_head *pages,
+ unsigned *nr_pages)
+{
+ return -ENOBUFS;
+}
+
+#endif /* CONFIG_NFS_FSCACHE */
+#endif /* _NFS_FSCACHE_H */
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index c99f654..0cd2b84 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -46,6 +46,7 @@
#include "delegation.h"
#include "iostat.h"
#include "internal.h"
+#include "fscache.h"
#define NFSDBG_FACILITY NFSDBG_VFS
@@ -88,6 +89,7 @@ void nfs_clear_inode(struct inode *inode)
BUG_ON(atomic_read(&NFS_I(inode)->data_updates) != 0);
nfs_zap_acl_cache(inode);
nfs_access_zap_cache(inode);
+ nfs_fscache_release_fh_cookie(inode);
}
/**
@@ -298,6 +300,8 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
memset(nfsi->cookieverf, 0, sizeof(nfsi->cookieverf));
nfsi->access_cache = RB_ROOT;
+ nfs_fscache_init_fh_cookie(inode);
+
unlock_new_inode(inode);
} else
nfs_refresh_inode(inode, fattr);
@@ -563,6 +567,7 @@ int nfs_open(struct inode *inode, struct file *filp)
ctx->mode = filp->f_mode;
nfs_file_set_open_context(filp, ctx);
put_nfs_open_context(ctx);
+ nfs_fscache_set_fh_cookie(inode, filp);
return 0;
}
@@ -629,7 +634,13 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)
(long long)NFS_FILEID(inode), status);
goto out;
}
- spin_unlock(&inode->i_lock);
+ if (nfsi->cache_validity & NFS_INO_INVALID_FSCACHE_ATTR) {
+ nfsi->cache_validity &= ~NFS_INO_INVALID_FSCACHE_ATTR;
+ spin_unlock(&inode->i_lock);
+ nfs_fscache_attr_changed(inode);
+ } else {
+ spin_unlock(&inode->i_lock);
+ }
if (nfsi->cache_validity & NFS_INO_INVALID_ACL)
nfs_zap_acl_cache(inode);
@@ -688,6 +699,7 @@ static int nfs_invalidate_mapping_nolock(struct inode *inode, struct address_spa
}
spin_unlock(&inode->i_lock);
nfs_inc_stats(inode, NFSIOS_DATAINVALIDATE);
+ nfs_fscache_renew_fh_cookie(inode);
dfprintk(PAGECACHE, "NFS: (%s/%Ld) data cache invalidated\n",
inode->i_sb->s_id, (long long)NFS_FILEID(inode));
return 0;
@@ -888,7 +900,13 @@ int nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
else
status = nfs_check_inode_attributes(inode, fattr);
- spin_unlock(&inode->i_lock);
+ if (nfsi->cache_validity & NFS_INO_INVALID_FSCACHE_ATTR) {
+ nfsi->cache_validity &= ~NFS_INO_INVALID_FSCACHE_ATTR;
+ spin_unlock(&inode->i_lock);
+ nfs_fscache_attr_changed(inode);
+ } else {
+ spin_unlock(&inode->i_lock);
+ }
return status;
}
@@ -918,7 +936,13 @@ int nfs_post_op_update_inode(struct inode *inode, struct nfs_fattr *fattr)
}
status = nfs_update_inode(inode, fattr);
out:
- spin_unlock(&inode->i_lock);
+ if (nfsi->cache_validity & NFS_INO_INVALID_FSCACHE_ATTR) {
+ nfsi->cache_validity &= ~NFS_INO_INVALID_FSCACHE_ATTR;
+ spin_unlock(&inode->i_lock);
+ nfs_fscache_attr_changed(inode);
+ } else {
+ spin_unlock(&inode->i_lock);
+ }
return status;
}
@@ -989,12 +1013,14 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
/* No, but did we race with nfs_end_data_update()? */
if (data_stable) {
inode->i_size = new_isize;
- invalid |= NFS_INO_INVALID_DATA;
+ invalid |= NFS_INO_INVALID_DATA |
+ NFS_INO_INVALID_FSCACHE_ATTR;
}
invalid |= NFS_INO_INVALID_ATTR;
} else if (new_isize > cur_isize) {
inode->i_size = new_isize;
- invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
+ invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA |
+ NFS_INO_INVALID_FSCACHE_ATTR;
}
nfsi->cache_change_attribute = now;
dprintk("NFS: isize change on server for file %s/%ld\n",
@@ -1006,7 +1032,8 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));
dprintk("NFS: mtime change on server for file %s/%ld\n",
inode->i_sb->s_id, inode->i_ino);
- invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
+ invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA |
+ NFS_INO_INVALID_FSCACHE_ATTR;
nfsi->cache_change_attribute = now;
}
@@ -1059,7 +1086,7 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
/* Don't invalidate the data if we were to blame */
if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)
|| S_ISLNK(inode->i_mode)))
- invalid &= ~NFS_INO_INVALID_DATA;
+ invalid &= ~(NFS_INO_INVALID_DATA | NFS_INO_INVALID_FSCACHE_ATTR);
if (data_stable)
invalid &= ~(NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ATIME|NFS_INO_REVAL_PAGECACHE);
if (!nfs_have_delegation(inode, FMODE_READ) ||
@@ -1181,6 +1208,10 @@ static int __init init_nfs_fs(void)
{
int err;
+ err = nfs_fscache_register();
+ if (err < 0)
+ goto out6;
+
err = nfs_fs_proc_init();
if (err)
goto out5;
@@ -1227,6 +1258,8 @@ out3:
out4:
nfs_fs_proc_exit();
out5:
+ nfs_fscache_unregister();
+out6:
return err;
}
@@ -1237,6 +1270,7 @@ static void __exit exit_nfs_fs(void)
nfs_destroy_readpagecache();
nfs_destroy_inodecache();
nfs_destroy_nfspagecache();
+ nfs_fscache_unregister();
#ifdef CONFIG_PROC_FS
rpc_proc_unregister("nfs");
#endif
diff --git a/fs/nfs/read.c b/fs/nfs/read.c
index 19e0563..4dfd6dc 100644
--- a/fs/nfs/read.c
+++ b/fs/nfs/read.c
@@ -18,12 +18,14 @@
#include <linux/sunrpc/clnt.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_page.h>
+#include <linux/nfs_mount.h>
#include <linux/smp_lock.h>
#include <asm/system.h>
#include "internal.h"
#include "iostat.h"
+#include "fscache.h"
#define NFSDBG_FACILITY NFSDBG_PAGECACHE
@@ -114,8 +116,8 @@ static void nfs_readpage_truncate_uninitialised_page(struct nfs_read_data *data)
}
}
-static int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode,
- struct page *page)
+int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode,
+ struct page *page)
{
LIST_HEAD(one_request);
struct nfs_page *new;
@@ -142,6 +144,11 @@ static int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode,
static void nfs_readpage_release(struct nfs_page *req)
{
+ struct inode *d_inode = req->wb_context->path.dentry->d_inode;
+
+ if (PageUptodate(req->wb_page))
+ nfs_readpage_to_fscache(d_inode, req->wb_page, 0);
+
unlock_page(req->wb_page);
dprintk("NFS: read done (%s/%Ld %d@%Ld)\n",
@@ -500,8 +507,15 @@ int nfs_readpage(struct file *file, struct page *page)
ctx = get_nfs_open_context((struct nfs_open_context *)
file->private_data);
+ if (!IS_SYNC(inode)) {
+ error = nfs_readpage_from_fscache(ctx, inode, page);
+ if (error == 0)
+ goto out;
+ }
+
error = nfs_readpage_async(ctx, inode, page);
+out:
put_nfs_open_context(ctx);
return error;
out_unlock:
@@ -578,6 +592,15 @@ int nfs_readpages(struct file *filp, struct address_space *mapping,
} else
desc.ctx = get_nfs_open_context((struct nfs_open_context *)
filp->private_data);
+
+ /* attempt to read as many of the pages as possible from the cache
+ * - this returns -ENOBUFS immediately if the cookie is negative
+ */
+ ret = nfs_readpages_from_fscache(desc.ctx, inode, mapping,
+ pages, &nr_pages);
+ if (ret == 0)
+ goto read_complete; /* all pages were read */
+
if (rsize < PAGE_CACHE_SIZE)
nfs_pageio_init(&pgio, inode, nfs_pagein_multi, rsize, 0);
else
@@ -588,6 +611,7 @@ int nfs_readpages(struct file *filp, struct address_space *mapping,
nfs_pageio_complete(&pgio);
npages = (pgio.pg_bytes_written + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
nfs_add_stats(inode, NFSIOS_READPAGES, npages);
+read_complete:
put_nfs_open_context(desc.ctx);
out:
return ret;
diff --git a/fs/nfs/sysctl.c b/fs/nfs/sysctl.c
index b62481d..9eaf5a9 100644
--- a/fs/nfs/sysctl.c
+++ b/fs/nfs/sysctl.c
@@ -14,6 +14,8 @@
#include <linux/nfs_fs.h>
#include "callback.h"
+#include "internal.h"
+#include "fscache.h"
static const int nfs_set_port_min = 0;
static const int nfs_set_port_max = 65535;
@@ -58,6 +60,48 @@ static ctl_table nfs_cb_sysctls[] = {
.mode = 0644,
.proc_handler = &proc_dointvec,
},
+#ifdef CONFIG_NFS_FSCACHE
+ {
+ .ctl_name = CTL_UNNUMBERED,
+ .procname = "fscache_from_error",
+ .data = &nfs_fscache_from_error,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = CTL_UNNUMBERED,
+ .procname = "fscache_to_error",
+ .data = &nfs_fscache_to_error,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = CTL_UNNUMBERED,
+ .procname = "fscache_uncache_page",
+ .data = &nfs_fscache_uncache_page,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = CTL_UNNUMBERED,
+ .procname = "fscache_to_pages",
+ .data = &nfs_fscache_to_pages,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec_minmax,
+ },
+ {
+ .ctl_name = CTL_UNNUMBERED,
+ .procname = "fscache_from_pages",
+ .data = &nfs_fscache_from_pages,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+#endif
{ .ctl_name = 0 }
};
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index 7250eea..19419bc 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -172,6 +172,9 @@ struct nfs_inode {
int delegation_state;
struct rw_semaphore rwsem;
#endif /* CONFIG_NFS_V4*/
+#ifdef CONFIG_NFS_FSCACHE
+ struct fscache_cookie *fscache;
+#endif
struct inode vfs_inode;
};
@@ -185,6 +188,7 @@ struct nfs_inode {
#define NFS_INO_INVALID_ACL 0x0010 /* cached acls are invalid */
#define NFS_INO_REVAL_PAGECACHE 0x0020 /* must revalidate pagecache */
#define NFS_INO_REVAL_FORCED 0x0040 /* force revalidation ignoring a delegation */
+#define NFS_INO_INVALID_FSCACHE_ATTR 0x0080 /* local cache attributes are invalid */
/*
* Bit offsets in flags field
@@ -193,6 +197,7 @@ struct nfs_inode {
#define NFS_INO_ADVISE_RDPLUS (1) /* advise readdirplus */
#define NFS_INO_STALE (2) /* possible stale inode */
#define NFS_INO_ACL_LRU_SET (3) /* Inode is on the LRU list */
+#define NFS_INO_FSCACHE (4) /* inode can be cached by FS-Cache */
static inline struct nfs_inode *NFS_I(struct inode *inode)
{
@@ -218,6 +223,7 @@ static inline struct nfs_inode *NFS_I(struct inode *inode)
#define NFS_FLAGS(inode) (NFS_I(inode)->flags)
#define NFS_STALE(inode) (test_bit(NFS_INO_STALE, &NFS_FLAGS(inode)))
+#define NFS_FSCACHE(inode) (test_bit(NFS_INO_FSCACHE, &NFS_FLAGS(inode)))
#define NFS_FILEID(inode) (NFS_I(inode)->fileid)
@@ -464,6 +470,7 @@ extern int nfs_readpages(struct file *, struct address_space *,
struct list_head *, unsigned);
extern int nfs_readpage_result(struct rpc_task *, struct nfs_read_data *);
extern void nfs_readdata_release(void *data);
+extern int nfs_readpage_async(struct nfs_open_context *, struct inode *, struct page *);
/*
* Allocate nfs_read_data structures
@@ -554,6 +561,7 @@ extern void * nfs_root_data(void);
#define NFSDBG_CALLBACK 0x0100
#define NFSDBG_CLIENT 0x0200
#define NFSDBG_MOUNT 0x0400
+#define NFSDBG_FSCACHE 0x0800
#define NFSDBG_ALL 0xFFFF
#ifdef __KERNEL__
diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index 0cac49b..95d0617 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -3,6 +3,7 @@
#include <linux/list.h>
#include <linux/backing-dev.h>
+#include <linux/fscache.h>
struct nfs_iostats;
@@ -65,6 +66,10 @@ struct nfs_client {
char cl_ipaddr[16];
unsigned char cl_id_uniquifier;
#endif
+
+#ifdef CONFIG_NFS_FSCACHE
+ struct fscache_cookie *fscache; /* client index cache cookie */
+#endif
};
/*
@@ -95,6 +100,8 @@ struct nfs_server {
unsigned int acdirmin;
unsigned int acdirmax;
unsigned int namelen;
+ unsigned int options; /* extra options enabled by mount */
+#define NFS_OPTION_FSCACHE 0x00000001 /* - local caching enabled */
struct nfs_fsid fsid;
__u64 maxfilesize; /* maximum file size */
Add a function to install a monitor on the page lock waitqueue for a particular
page, thus allowing the page being unlocked to be detected.
This is used by CacheFiles to detect read completion on a page in the backing
filesystem so that it can then copy the data to the waiting netfs page.
Signed-Off-By: David Howells <[email protected]>
---
include/linux/pagemap.h | 5 +++++
mm/filemap.c | 19 +++++++++++++++++++
2 files changed, 24 insertions(+), 0 deletions(-)
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index d1049b6..452fdcf 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -220,6 +220,11 @@ static inline void wait_on_page_fscache_write(struct page *page)
extern void end_page_fscache_write(struct page *page);
/*
+ * Add an arbitrary waiter to a page's wait queue
+ */
+extern void add_page_wait_queue(struct page *page, wait_queue_t *waiter);
+
+/*
* Fault a userspace page into pagetables. Return non-zero on a fault.
*
* This assumes that two userspace pages are always sufficient. That's
diff --git a/mm/filemap.c b/mm/filemap.c
index 21aeee9..e48e862 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -518,6 +518,25 @@ void fastcall wait_on_page_bit(struct page *page, int bit_nr)
EXPORT_SYMBOL(wait_on_page_bit);
/**
+ * add_page_wait_queue - Add an arbitrary waiter to a page's wait queue
+ * @page - Page defining the wait queue of interest
+ * @waiter - Waiter to add to the queue
+ *
+ * Add an arbitrary @waiter to the wait queue for the nominated @page.
+ */
+void add_page_wait_queue(struct page *page, wait_queue_t *waiter)
+{
+ wait_queue_head_t *q = page_waitqueue(page);
+ unsigned long flags;
+
+ spin_lock_irqsave(&q->lock, flags);
+ __add_wait_queue(q, waiter);
+ spin_unlock_irqrestore(&q->lock, flags);
+}
+
+EXPORT_SYMBOL_GPL(add_page_wait_queue);
+
+/**
* unlock_page - unlock a locked page
* @page: the page
*
Add an address space operation to write one single page of data to an inode at
a page-aligned location (thus permitting the implementation to be highly
optimised).
This is used by CacheFiles to store the contents of netfs pages into their
backing file pages.
Supply a generic implementation for this that uses the prepare_write() and
commit_write() address_space operations to bound a copy directly into the page
cache.
Hook the Ext2 and Ext3 operations to the generic implementation.
Signed-Off-By: David Howells <[email protected]>
---
fs/ext2/inode.c | 2 +
fs/ext3/inode.c | 3 ++
include/linux/fs.h | 7 ++++
mm/filemap.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 107 insertions(+), 0 deletions(-)
diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 0079b2c..b3e4b50 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -695,6 +695,7 @@ const struct address_space_operations ext2_aops = {
.direct_IO = ext2_direct_IO,
.writepages = ext2_writepages,
.migratepage = buffer_migrate_page,
+ .write_one_page = generic_file_buffered_write_one_page,
};
const struct address_space_operations ext2_aops_xip = {
@@ -713,6 +714,7 @@ const struct address_space_operations ext2_nobh_aops = {
.direct_IO = ext2_direct_IO,
.writepages = ext2_writepages,
.migratepage = buffer_migrate_page,
+ .write_one_page = generic_file_buffered_write_one_page,
};
/*
diff --git a/fs/ext3/inode.c b/fs/ext3/inode.c
index de4e316..93809eb 100644
--- a/fs/ext3/inode.c
+++ b/fs/ext3/inode.c
@@ -1713,6 +1713,7 @@ static const struct address_space_operations ext3_ordered_aops = {
.releasepage = ext3_releasepage,
.direct_IO = ext3_direct_IO,
.migratepage = buffer_migrate_page,
+ .write_one_page = generic_file_buffered_write_one_page,
};
static const struct address_space_operations ext3_writeback_aops = {
@@ -1727,6 +1728,7 @@ static const struct address_space_operations ext3_writeback_aops = {
.releasepage = ext3_releasepage,
.direct_IO = ext3_direct_IO,
.migratepage = buffer_migrate_page,
+ .write_one_page = generic_file_buffered_write_one_page,
};
static const struct address_space_operations ext3_journalled_aops = {
@@ -1740,6 +1742,7 @@ static const struct address_space_operations ext3_journalled_aops = {
.bmap = ext3_bmap,
.invalidatepage = ext3_invalidatepage,
.releasepage = ext3_releasepage,
+ .write_one_page = generic_file_buffered_write_one_page,
};
void ext3_set_aops(struct inode *inode)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index bf35441..38f67ac 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -434,6 +434,11 @@ struct address_space_operations {
int (*migratepage) (struct address_space *,
struct page *, struct page *);
int (*launder_page) (struct page *);
+ /* write the contents of the source page over the page at the specified
+ * index in the target address space (the source page does not need to
+ * be related to the target address space) */
+ int (*write_one_page)(struct address_space *, pgoff_t, struct page *);
+
};
struct backing_dev_info;
@@ -1669,6 +1674,8 @@ extern ssize_t generic_file_direct_write(struct kiocb *, const struct iovec *,
unsigned long *, loff_t, loff_t *, size_t, size_t);
extern ssize_t generic_file_buffered_write(struct kiocb *, const struct iovec *,
unsigned long, loff_t, loff_t *, size_t, ssize_t);
+extern int generic_file_buffered_write_one_page(struct address_space *,
+ pgoff_t, struct page *);
extern ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos);
extern ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos);
extern void do_generic_mapping_read(struct address_space *mapping,
diff --git a/mm/filemap.c b/mm/filemap.c
index 3a61923..21aeee9 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -2016,6 +2016,101 @@ zero_length_segment:
}
EXPORT_SYMBOL(generic_file_buffered_write);
+/**
+ * generic_file_buffered_write_one_page - Write a single page of data to an
+ * inode
+ * @mapping - The address space of the target inode
+ * @index - The target page in the target inode to fill
+ * @source - The data to write into the target page
+ *
+ * Write the data from the source page to the page in the nominated address
+ * space at the @index specified. Note that the file will not be extended if
+ * the page crosses the EOF marker, in which case only the first part of the
+ * page will be written.
+ *
+ * The @source page does not need to have any association with the file or the
+ * target page offset.
+ */
+int generic_file_buffered_write_one_page(struct address_space *mapping,
+ pgoff_t index,
+ struct page *source)
+{
+ const struct address_space_operations *a_ops = mapping->a_ops;
+ struct pagevec lru_pvec;
+ struct page *page, *cached_page = NULL;
+ unsigned psize;
+ loff_t isize;
+ long status = 0;
+
+ pagevec_init(&lru_pvec, 0);
+
+ page = __grab_cache_page(mapping, index, &cached_page, &lru_pvec);
+ if (!page) {
+ BUG_ON(cached_page);
+ return -ENOMEM;
+ }
+
+ psize = PAGE_CACHE_SIZE;
+ isize = i_size_read(mapping->host);
+ if ((isize >> PAGE_SHIFT) == index)
+ psize = isize & (PAGE_SIZE - 1);
+
+ status = a_ops->prepare_write(NULL, page, 0, psize);
+ if (unlikely(status)) {
+ isize = i_size_read(mapping->host);
+
+ if (status != AOP_TRUNCATED_PAGE)
+ unlock_page(page);
+ page_cache_release(page);
+ if (status == AOP_TRUNCATED_PAGE)
+ goto sync;
+
+ /* prepare_write() may have instantiated a few blocks outside
+ * i_size. Trim these off again.
+ */
+ if ((1ULL << (index + 1)) > isize)
+ vmtruncate(mapping->host, isize);
+ goto sync;
+ }
+
+ copy_highpage(page, source);
+ flush_dcache_page(page);
+
+ status = a_ops->commit_write(NULL, page, 0, psize);
+ if (status == AOP_TRUNCATED_PAGE) {
+ page_cache_release(page);
+ goto sync;
+ }
+
+ if (status > 0)
+ status = 0;
+
+ unlock_page(page);
+ mark_page_accessed(page);
+ page_cache_release(page);
+ if (status < 0)
+ return status;
+
+ balance_dirty_pages_ratelimited(mapping);
+ cond_resched();
+
+sync:
+ if (cached_page)
+ page_cache_release(cached_page);
+
+ /* the caller must handle O_SYNC themselves, but we handle S_SYNC and
+ * MS_SYNCHRONOUS here */
+ if (unlikely(IS_SYNC(mapping->host)) && !a_ops->writepage)
+ status = generic_osync_inode(mapping->host, mapping,
+ OSYNC_METADATA | OSYNC_DATA);
+
+ /* the caller must handle O_DIRECT for themselves */
+
+ pagevec_lru_add(&lru_pvec);
+ return status;
+}
+EXPORT_SYMBOL(generic_file_buffered_write_one_page);
+
static ssize_t
__generic_file_aio_write_nolock(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t *ppos)
Add an FS-Cache cache-backend that permits a mounted filesystem to be used as a
backing store for the cache.
CacheFiles uses a userspace daemon to do some of the cache management - such as
reaping stale nodes and culling. This is called cachefilesd and lives in
/sbin. The source for the daemon can be downloaded from:
http://people.redhat.com/~dhowells/cachefs/cachefilesd.c
And an example configuration from:
http://people.redhat.com/~dhowells/cachefs/cachefilesd.conf
The filesystem and data integrity of the cache are only as good as those of the
filesystem providing the backing services. Note that CacheFiles does not
attempt to journal anything since the journalling interfaces of the various
filesystems are very specific in nature.
CacheFiles creates a proc-file - "/proc/fs/cachefiles" - that is used for
communication with the daemon. Only one thing may have this open at once, and
whilst it is open, a cache is at least partially in existence. The daemon
opens this and sends commands down it to control the cache.
CacheFiles is currently limited to a single cache.
CacheFiles attempts to maintain at least a certain percentage of free space on
the filesystem, shrinking the cache by culling the objects it contains to make
space if necessary - see the "Cache Culling" section. This means it can be
placed on the same medium as a live set of data, and will expand to make use of
spare space and automatically contract when the set of data requires more
space.
============
REQUIREMENTS
============
The use of CacheFiles and its daemon requires the following features to be
available in the system and in the cache filesystem:
- dnotify.
- extended attributes (xattrs).
- openat() and friends.
- bmap() support on files in the filesystem (FIBMAP ioctl).
- The use of bmap() to detect a partial page at the end of the file.
It is strongly recommended that the "dir_index" option is enabled on Ext3
filesystems being used as a cache.
=============
CONFIGURATION
=============
The cache is configured by a script in /etc/cachefilesd.conf. These commands
set up cache ready for use. The following script commands are available:
(*) brun <N>%
(*) bcull <N>%
(*) bstop <N>%
Configure the culling limits. Optional. See the section on culling
The defaults are 7%, 5% and 1% respectively.
(*) dir <path>
Specify the directory containing the root of the cache. Mandatory.
(*) tag <name>
Specify a tag to FS-Cache to use in distinguishing multiple caches.
Optional. The default is "CacheFiles".
(*) debug <mask>
Specify a numeric bitmask to control debugging in the kernel module.
Optional. The default is zero (all off).
==================
STARTING THE CACHE
==================
The cache is started by running the daemon. The daemon opens the cache proc
file, configures the cache and tells it to begin caching. At that point the
cache binds to fscache and the cache becomes live.
The daemon is run as follows:
/sbin/cachefilesd [-d]* [-s] [-n] [-f <configfile>]
The flags are:
(*) -d
Increase the debugging level. This can be specified multiple times and
is cumulative with itself.
(*) -s
Send messages to stderr instead of syslog.
(*) -n
Don't daemonise and go into background.
(*) -f <configfile>
Use an alternative configuration file rather than the default one.
===============
THINGS TO AVOID
===============
Do not mount other things within the cache as this will cause problems. The
kernel module contains its own very cut-down path walking facility that ignores
mountpoints, but the daemon can't avoid them.
Do not create, rename or unlink files and directories in the cache whilst the
cache is active, as this may cause the state to become uncertain.
Renaming files in the cache might make objects appear to be other objects (the
filename is part of the lookup key).
Do not change or remove the extended attributes attached to cache files by the
cache as this will cause the cache state management to get confused.
Do not create files or directories in the cache, lest the cache get confused or
serve incorrect data.
Do not chmod files in the cache. The module creates things with minimal
permissions to prevent random users being able to access them directly.
=============
CACHE CULLING
=============
The cache may need culling occasionally to make space. This involves
discarding objects from the cache that have been used less recently than
anything else. Culling is based on the access time of data objects. Empty
directories are culled if not in use.
Cache culling is done on the basis of the percentage of blocks available in the
underlying filesystem. There are three "limits":
(*) brun
If the amount of available space in the cache rises above this limit, then
culling is turned off.
(*) bcull
If the amount of available space in the cache falls below this limit, then
culling is started.
(*) bstop
If the amount of available space in the cache falls below this limit, then
no further allocation of disk space is permitted until culling has raised
the amount above this limit again.
These must be configured thusly:
0 <= bstop < bcull < brun < 100
Note that these are percentages of available space, and do _not_ appear as 100
minus the percentage displayed by the "df" program.
The userspace daemon scans the cache to build up a table of cullable objects.
These are then culled in least recently used order. A new scan of the cache is
started as soon as space is made in the table. Objects will be skipped if
their atimes have changed or if the kernel module says it is still using them.
===============
CACHE STRUCTURE
===============
The CacheFiles module will create two directories in the directory it was
given:
(*) cache/
(*) graveyard/
The active cache objects all reside in the first directory. The CacheFiles
kernel module moves any retired or culled objects that it can't simply unlink
to the graveyard from which the daemon will actually delete them.
The daemon uses dnotify to monitor the graveyard directory, and will delete
anything that appears therein.
The module represents index objects as directories with the filename "I..." or
"J...". Note that the "cache/" directory is itself a special index.
Data objects are represented as files if they have no children, or directories
if they do. Their filenames all begin "D..." or "E...". If represented as a
directory, data objects will have a file in the directory called "data" that
actually holds the data.
Special objects are similar to data objects, except their filenames begin
"S..." or "T...".
If an object has children, then it will be represented as a directory.
Immediately in the representative directory are a collection of directories
named for hash values of the child object keys with an '@' prepended. Into
this directory, if possible, will be placed the representations of the child
objects:
INDEX INDEX INDEX DATA FILES
========= ========== ================================= ================
cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400
cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400/@75/Es0g000w...DB1ry
cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400/@75/Es0g000w...N22ry
cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400/@75/Es0g000w...FP1ry
If the key is so long that it exceeds NAME_MAX with the decorations added on to
it, then it will be cut into pieces, the first few of which will be used to
make a nest of directories, and the last one of which will be the objects
inside the last directory. The names of the intermediate directories will have
'+' prepended:
J1223/@23/+xy...z/+kl...m/Epqr
Note that keys are raw data, and not only may they exceed NAME_MAX in size,
they may also contain things like '/' and NUL characters, and so they may not
be suitable for turning directly into a filename.
To handle this, CacheFiles will use a suitably printable filename directly and
"base-64" encode ones that aren't directly suitable. The two versions of
object filenames indicate the encoding:
OBJECT TYPE PRINTABLE ENCODED
=============== =============== ===============
Index "I..." "J..."
Data "D..." "E..."
Special "S..." "T..."
Intermediate directories are always "@" or "+" as appropriate.
Each object in the cache has an extended attribute label that holds the object
type ID (required to distinguish special objects) and the auxiliary data from
the netfs. The latter is used to detect stale objects in the cache and update
or retire them.
Note that CacheFiles will erase from the cache any file it doesn't recognise or
any file of an incorrect type (such as a FIFO file or a device file).
This documentation is added by the patch to:
Documentation/filesystems/caching/cachefiles.txt
Signed-Off-By: David Howells <[email protected]>
---
Documentation/filesystems/caching/cachefiles.txt | 395 ++++++++++
fs/Kconfig | 1
fs/Makefile | 1
fs/cachefiles/Kconfig | 33 +
fs/cachefiles/Makefile | 18
fs/cachefiles/cf-bind.c | 289 +++++++
fs/cachefiles/cf-daemon.c | 720 +++++++++++++++++++
fs/cachefiles/cf-interface.c | 442 +++++++++++
fs/cachefiles/cf-internal.h | 369 ++++++++++
fs/cachefiles/cf-key.c | 159 ++++
fs/cachefiles/cf-main.c | 109 +++
fs/cachefiles/cf-namei.c | 743 +++++++++++++++++++
fs/cachefiles/cf-proc.c | 166 ++++
fs/cachefiles/cf-rdwr.c | 849 ++++++++++++++++++++++
fs/cachefiles/cf-security.c | 94 ++
fs/cachefiles/cf-xattr.c | 292 ++++++++
16 files changed, 4680 insertions(+), 0 deletions(-)
diff --git a/Documentation/filesystems/caching/cachefiles.txt b/Documentation/filesystems/caching/cachefiles.txt
new file mode 100644
index 0000000..b502cff
--- /dev/null
+++ b/Documentation/filesystems/caching/cachefiles.txt
@@ -0,0 +1,395 @@
+ ===============================================
+ CacheFiles: CACHE ON ALREADY MOUNTED FILESYSTEM
+ ===============================================
+
+Contents:
+
+ (*) Overview.
+
+ (*) Requirements.
+
+ (*) Configuration.
+
+ (*) Starting the cache.
+
+ (*) Things to avoid.
+
+ (*) Cache culling.
+
+ (*) Cache structure.
+
+ (*) Security model and SELinux.
+
+========
+OVERVIEW
+========
+
+CacheFiles is a caching backend that's meant to use as a cache a directory on
+an already mounted filesystem of a local type (such as Ext3).
+
+CacheFiles uses a userspace daemon to do some of the cache management - such as
+reaping stale nodes and culling. This is called cachefilesd and lives in
+/sbin.
+
+The filesystem and data integrity of the cache are only as good as those of the
+filesystem providing the backing services. Note that CacheFiles does not
+attempt to journal anything since the journalling interfaces of the various
+filesystems are very specific in nature.
+
+CacheFiles creates a misc character device - "/dev/cachefiles" - that is used
+to communication with the daemon. Only one thing may have this open at once,
+and whilst it is open, a cache is at least partially in existence. The daemon
+opens this and sends commands down it to control the cache.
+
+CacheFiles is currently limited to a single cache.
+
+CacheFiles attempts to maintain at least a certain percentage of free space on
+the filesystem, shrinking the cache by culling the objects it contains to make
+space if necessary - see the "Cache Culling" section. This means it can be
+placed on the same medium as a live set of data, and will expand to make use of
+spare space and automatically contract when the set of data requires more
+space.
+
+
+============
+REQUIREMENTS
+============
+
+The use of CacheFiles and its daemon requires the following features to be
+available in the system and in the cache filesystem:
+
+ - dnotify.
+
+ - extended attributes (xattrs).
+
+ - openat() and friends.
+
+ - bmap() support on files in the filesystem (FIBMAP ioctl).
+
+ - The use of bmap() to detect a partial page at the end of the file.
+
+It is strongly recommended that the "dir_index" option is enabled on Ext3
+filesystems being used as a cache.
+
+
+=============
+CONFIGURATION
+=============
+
+The cache is configured by a script in /etc/cachefilesd.conf. These commands
+set up cache ready for use. The following script commands are available:
+
+ (*) brun <N>%
+ (*) bcull <N>%
+ (*) bstop <N>%
+ (*) frun <N>%
+ (*) fcull <N>%
+ (*) fstop <N>%
+
+ Configure the culling limits. Optional. See the section on culling
+ The defaults are 7% (run), 5% (cull) and 1% (stop) respectively.
+
+ The commands beginning with a 'b' are file space (block) limits, those
+ beginning with an 'f' are file count limits.
+
+ (*) dir <path>
+
+ Specify the directory containing the root of the cache. Mandatory.
+
+ (*) tag <name>
+
+ Specify a tag to FS-Cache to use in distinguishing multiple caches.
+ Optional. The default is "CacheFiles".
+
+ (*) debug <mask>
+
+ Specify a numeric bitmask to control debugging in the kernel module.
+ Optional. The default is zero (all off). The following values can be
+ OR'd into the mask to collect various information:
+
+ 1 Turn on trace of function entry (_enter() macros)
+ 2 Turn on trace of function exit (_leave() macros)
+ 4 Turn on trace of internal debug points (_debug())
+
+ This mask can also be set through sysfs, eg:
+
+ echo 5 >/sys/modules/cachefiles/parameters/debug
+
+
+==================
+STARTING THE CACHE
+==================
+
+The cache is started by running the daemon. The daemon opens the cache device,
+configures the cache and tells it to begin caching. At that point the cache
+binds to fscache and the cache becomes live.
+
+The daemon is run as follows:
+
+ /sbin/cachefilesd [-d]* [-s] [-n] [-f <configfile>]
+
+The flags are:
+
+ (*) -d
+
+ Increase the debugging level. This can be specified multiple times and
+ is cumulative with itself.
+
+ (*) -s
+
+ Send messages to stderr instead of syslog.
+
+ (*) -n
+
+ Don't daemonise and go into background.
+
+ (*) -f <configfile>
+
+ Use an alternative configuration file rather than the default one.
+
+
+===============
+THINGS TO AVOID
+===============
+
+Do not mount other things within the cache as this will cause problems. The
+kernel module contains its own very cut-down path walking facility that ignores
+mountpoints, but the daemon can't avoid them.
+
+Do not create, rename or unlink files and directories in the cache whilst the
+cache is active, as this may cause the state to become uncertain.
+
+Renaming files in the cache might make objects appear to be other objects (the
+filename is part of the lookup key).
+
+Do not change or remove the extended attributes attached to cache files by the
+cache as this will cause the cache state management to get confused.
+
+Do not create files or directories in the cache, lest the cache get confused or
+serve incorrect data.
+
+Do not chmod files in the cache. The module creates things with minimal
+permissions to prevent random users being able to access them directly.
+
+
+=============
+CACHE CULLING
+=============
+
+The cache may need culling occasionally to make space. This involves
+discarding objects from the cache that have been used less recently than
+anything else. Culling is based on the access time of data objects. Empty
+directories are culled if not in use.
+
+Cache culling is done on the basis of the percentage of blocks and the
+percentage of files available in the underlying filesystem. There are six
+"limits":
+
+ (*) brun
+ (*) frun
+
+ If the amount of free space and the number of available files in the cache
+ rises above both these limits, then culling is turned off.
+
+ (*) bcull
+ (*) fcull
+
+ If the amount of available space or the number of available files in the
+ cache falls below either of these limits, then culling is started.
+
+ (*) bstop
+ (*) fstop
+
+ If the amount of available space or the number of available files in the
+ cache falls below either of these limits, then no further allocation of
+ disk space or files is permitted until culling has raised things above
+ these limits again.
+
+These must be configured thusly:
+
+ 0 <= bstop < bcull < brun < 100
+ 0 <= fstop < fcull < frun < 100
+
+Note that these are percentages of available space and available files, and do
+_not_ appear as 100 minus the percentage displayed by the "df" program.
+
+The userspace daemon scans the cache to build up a table of cullable objects.
+These are then culled in least recently used order. A new scan of the cache is
+started as soon as space is made in the table. Objects will be skipped if
+their atimes have changed or if the kernel module says it is still using them.
+
+
+===============
+CACHE STRUCTURE
+===============
+
+The CacheFiles module will create two directories in the directory it was
+given:
+
+ (*) cache/
+
+ (*) graveyard/
+
+The active cache objects all reside in the first directory. The CacheFiles
+kernel module moves any retired or culled objects that it can't simply unlink
+to the graveyard from which the daemon will actually delete them.
+
+The daemon uses dnotify to monitor the graveyard directory, and will delete
+anything that appears therein.
+
+
+The module represents index objects as directories with the filename "I..." or
+"J...". Note that the "cache/" directory is itself a special index.
+
+Data objects are represented as files if they have no children, or directories
+if they do. Their filenames all begin "D..." or "E...". If represented as a
+directory, data objects will have a file in the directory called "data" that
+actually holds the data.
+
+Special objects are similar to data objects, except their filenames begin
+"S..." or "T...".
+
+
+If an object has children, then it will be represented as a directory.
+Immediately in the representative directory are a collection of directories
+named for hash values of the child object keys with an '@' prepended. Into
+this directory, if possible, will be placed the representations of the child
+objects:
+
+ INDEX INDEX INDEX DATA FILES
+ ========= ========== ================================= ================
+ cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400
+ cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400/@75/Es0g000w...DB1ry
+ cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400/@75/Es0g000w...N22ry
+ cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400/@75/Es0g000w...FP1ry
+
+
+If the key is so long that it exceeds NAME_MAX with the decorations added on to
+it, then it will be cut into pieces, the first few of which will be used to
+make a nest of directories, and the last one of which will be the objects
+inside the last directory. The names of the intermediate directories will have
+'+' prepended:
+
+ J1223/@23/+xy...z/+kl...m/Epqr
+
+
+Note that keys are raw data, and not only may they exceed NAME_MAX in size,
+they may also contain things like '/' and NUL characters, and so they may not
+be suitable for turning directly into a filename.
+
+To handle this, CacheFiles will use a suitably printable filename directly and
+"base-64" encode ones that aren't directly suitable. The two versions of
+object filenames indicate the encoding:
+
+ OBJECT TYPE PRINTABLE ENCODED
+ =============== =============== ===============
+ Index "I..." "J..."
+ Data "D..." "E..."
+ Special "S..." "T..."
+
+Intermediate directories are always "@" or "+" as appropriate.
+
+
+Each object in the cache has an extended attribute label that holds the object
+type ID (required to distinguish special objects) and the auxiliary data from
+the netfs. The latter is used to detect stale objects in the cache and update
+or retire them.
+
+
+Note that CacheFiles will erase from the cache any file it doesn't recognise or
+any file of an incorrect type (such as a FIFO file or a device file).
+
+
+==========================
+SECURITY MODEL AND SELINUX
+==========================
+
+CacheFiles is implemented to deal properly with the LSM security features of
+the Linux kernel and the SELinux facility.
+
+One of the problems that CacheFiles faces is that it is generally acting on
+behalf of a process, and running in that process's context, and that includes a
+security context that is not appropriate for accessing the cache - either
+because the files in the cache are inaccessible to that process, or because if
+the process creates a file in the cache, that file may be inaccessible to other
+processes.
+
+The way CacheFiles works is to temporarily change the security context (fsuid,
+fsgid and actor security label) that the process acts as - without changing the
+security context of the process when it the target of an operation performed by
+some other process (so signalling and suchlike still work correctly).
+
+
+When the CacheFiles module is asked to bind to its cache, it:
+
+ (1) Finds the security label attached to the root cache directory and uses
+ that as the security label with which it will create files. By default,
+ this is:
+
+ cachefiles_var_t
+
+ (2) Finds the security label of the process which issued the bind request
+ (presumed to be the cachefilesd daemon), which by default will be:
+
+ cachefilesd_t
+
+ and asks LSM to supply a security ID as which it should act given the
+ daemon's label. By default, this will be:
+
+ cachefiles_kernel_t
+
+ SELinux transitions the daemon's security ID to the module's security ID
+ based on a rule of this form in the policy.
+
+ type_transition <daemon's-ID> kernel_t : process <module's-ID>;
+
+ For instance:
+
+ type_transition cachefilesd_t kernel_t : process cachefiles_kernel_t;
+
+
+The module's security ID gives it permission to create, move and remove files
+and directories in the cache, to find and access directories and files in the
+cache, to set and access extended attributes on cache objects, and to read and
+write files in the cache.
+
+The daemon's security ID gives it only a very restricted set of permissions: it
+may scan directories, stat files and erase files and directories. It may
+not read or write files in the cache, and so it is precluded from accessing the
+data cached therein; nor is it permitted to create new files in the cache.
+
+
+There are policy source files available in:
+
+ http://people.redhat.com/~dhowells/fscache/cachefilesd-0.8.tar.bz2
+
+and later versions. In that tarball, see the files:
+
+ cachefilesd.te
+ cachefilesd.fc
+ cachefilesd.if
+
+They are built and installed directly by the RPM.
+
+If a non-RPM based system is being used, then copy the above files to their own
+directory and run:
+
+ make -f /usr/share/selinux/devel/Makefile
+ semodule -i cachefilesd.pp
+
+You will need checkpolicy and selinux-policy-devel installed prior to the
+build.
+
+
+By default, the cache is located in /var/fscache, but if it is desirable that
+it should be elsewhere, than either the above policy files must be altered, or
+an auxiliary policy must be installed to label the alternate location of the
+cache.
+
+For instructions on how to add an auxiliary policy to enable the cache to be
+located elsewhere when SELinux is in enforcing mode, please see:
+
+ /usr/share/doc/cachefilesd-*/move-cache.txt
+
+When the cachefilesd rpm is installed; alternatively, the document can be found
+in the sources.
diff --git a/fs/Kconfig b/fs/Kconfig
index e367141..8ae7eda 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -631,6 +631,7 @@ config GENERIC_ACL
menu "Caches"
source "fs/fscache/Kconfig"
+source "fs/cachefiles/Kconfig"
endmenu
diff --git a/fs/Makefile b/fs/Makefile
index 4eecc9a..becdb4f 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_AFS_FS) += afs/
obj-$(CONFIG_BEFS_FS) += befs/
obj-$(CONFIG_HOSTFS) += hostfs/
obj-$(CONFIG_HPPFS) += hppfs/
+obj-$(CONFIG_CACHEFILES) += cachefiles/
obj-$(CONFIG_DEBUG_FS) += debugfs/
obj-$(CONFIG_OCFS2_FS) += ocfs2/
obj-$(CONFIG_GFS2_FS) += gfs2/
diff --git a/fs/cachefiles/Kconfig b/fs/cachefiles/Kconfig
new file mode 100644
index 0000000..ddbdd85
--- /dev/null
+++ b/fs/cachefiles/Kconfig
@@ -0,0 +1,33 @@
+
+config CACHEFILES
+ tristate "Filesystem caching on files"
+ depends on FSCACHE
+ help
+ This permits use of a mounted filesystem as a cache for other
+ filesystems - primarily networking filesystems - thus allowing fast
+ local disk to enhance the speed of slower devices.
+
+ See Documentation/filesystems/caching/cachefiles.txt for more
+ information.
+
+config CACHEFILES_DEBUG
+ bool "Debug CacheFiles"
+ depends on CACHEFILES
+ help
+ This permits debugging to be dynamically enabled in the filesystem
+ caching on files module. If this is set, the debugging output may be
+ enabled by setting bits in /sys/modules/cachefiles/parameter/debug or
+ by including a debugging specifier in /etc/cachefilesd.conf.
+
+config CACHEFILES_HISTOGRAM
+ bool "Gather latency information on CacheFiles"
+ depends on CACHEFILES && FSCACHE_PROC
+ help
+
+ This option causes latency information to be gathered on CacheFiles
+ operation and exported through file:
+
+ /proc/fs/fscache/cachefiles/histogram
+
+ See Documentation/filesystems/caching/cachefiles.txt for more
+ information.
diff --git a/fs/cachefiles/Makefile b/fs/cachefiles/Makefile
new file mode 100644
index 0000000..8a9c1bd
--- /dev/null
+++ b/fs/cachefiles/Makefile
@@ -0,0 +1,18 @@
+#
+# Makefile for caching in a mounted filesystem
+#
+
+cachefiles-y := \
+ cf-bind.o \
+ cf-daemon.o \
+ cf-interface.o \
+ cf-key.o \
+ cf-main.o \
+ cf-namei.o \
+ cf-rdwr.o \
+ cf-security.o \
+ cf-xattr.o
+
+cachefiles-$(CONFIG_CACHEFILES_HISTOGRAM) += cf-proc.o
+
+obj-$(CONFIG_CACHEFILES) := cachefiles.o
diff --git a/fs/cachefiles/cf-bind.c b/fs/cachefiles/cf-bind.c
new file mode 100644
index 0000000..b524e08
--- /dev/null
+++ b/fs/cachefiles/cf-bind.c
@@ -0,0 +1,289 @@
+/* Bind and unbind a cache from the filesystem backing it
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/statfs.h>
+#include <linux/ctype.h>
+#include "cf-internal.h"
+
+static int cachefiles_daemon_add_cache(struct cachefiles_cache *caches);
+
+/*
+ * bind a directory as a cache
+ */
+int cachefiles_daemon_bind(struct cachefiles_cache *cache, char *args)
+{
+ _enter("{%u,%u,%u,%u,%u,%u},%s",
+ cache->frun_percent,
+ cache->fcull_percent,
+ cache->fstop_percent,
+ cache->brun_percent,
+ cache->bcull_percent,
+ cache->bstop_percent,
+ args);
+
+ /* start by checking things over */
+ ASSERT(cache->fstop_percent >= 0 &&
+ cache->fstop_percent < cache->fcull_percent &&
+ cache->fcull_percent < cache->frun_percent &&
+ cache->frun_percent < 100);
+
+ ASSERT(cache->bstop_percent >= 0 &&
+ cache->bstop_percent < cache->bcull_percent &&
+ cache->bcull_percent < cache->brun_percent &&
+ cache->brun_percent < 100);
+
+ if (*args) {
+ kerror("'bind' command doesn't take an argument");
+ return -EINVAL;
+ }
+
+ if (!cache->rootdirname) {
+ kerror("No cache directory specified");
+ return -EINVAL;
+ }
+
+ /* don't permit already bound caches to be re-bound */
+ if (test_bit(CACHEFILES_READY, &cache->flags)) {
+ kerror("Cache already bound");
+ return -EBUSY;
+ }
+
+ /* make sure we have copies of the tag and dirname strings */
+ if (!cache->tag) {
+ /* the tag string is released by the fops->release()
+ * function, so we don't release it on error here */
+ cache->tag = kstrdup("CacheFiles", GFP_KERNEL);
+ if (!cache->tag)
+ return -ENOMEM;
+ }
+
+ /* add the cache */
+ return cachefiles_daemon_add_cache(cache);
+}
+
+/*
+ * add a cache
+ */
+static int cachefiles_daemon_add_cache(struct cachefiles_cache *cache)
+{
+ struct cachefiles_object *fsdef;
+ struct nameidata nd;
+ struct kstatfs stats;
+ struct dentry *graveyard, *cachedir, *root;
+ struct cred *saved_cred;
+ int ret;
+
+ _enter("");
+
+ /* we want to work under the module's security ID */
+ ret = cachefiles_get_security_ID(cache);
+ if (ret < 0)
+ return ret;
+
+ cachefiles_begin_secure(cache, &saved_cred);
+
+ /* allocate the root index object */
+ ret = -ENOMEM;
+
+ fsdef = kmem_cache_alloc(cachefiles_object_jar, GFP_KERNEL);
+ if (!fsdef)
+ goto error_root_object;
+
+ ASSERTCMP(fsdef->backer, ==, NULL);
+
+ atomic_set(&fsdef->usage, 1);
+ fsdef->type = FSCACHE_COOKIE_TYPE_INDEX;
+
+ _debug("- fsdef %p", fsdef);
+
+ /* look up the directory at the root of the cache */
+ memset(&nd, 0, sizeof(nd));
+
+ ret = path_lookup(cache->rootdirname, LOOKUP_DIRECTORY, &nd);
+ if (ret < 0)
+ goto error_open_root;
+
+ cache->mnt = mntget(nd.mnt);
+ root = dget(nd.dentry);
+ path_release(&nd);
+
+ /* check parameters */
+ ret = -EOPNOTSUPP;
+ if (!root->d_inode ||
+ !root->d_inode->i_op ||
+ !root->d_inode->i_op->lookup ||
+ !root->d_inode->i_op->mkdir ||
+ !root->d_inode->i_op->setxattr ||
+ !root->d_inode->i_op->getxattr ||
+ !root->d_sb ||
+ !root->d_sb->s_op ||
+ !root->d_sb->s_op->statfs ||
+ !root->d_sb->s_op->sync_fs)
+ goto error_unsupported;
+
+ ret = -EROFS;
+ if (root->d_sb->s_flags & MS_RDONLY)
+ goto error_unsupported;
+
+ /* determine the security of the on-disk cache as this governs
+ * security ID of files we create */
+ ret = cachefiles_determine_cache_secid(cache, root);
+ if (ret < 0)
+ goto error_unsupported;
+
+ cachefiles_end_secure(cache, saved_cred);
+ cachefiles_begin_secure(cache, &saved_cred);
+
+ /* get the cache size and blocksize */
+ ret = vfs_statfs(root, &stats);
+ if (ret < 0)
+ goto error_unsupported;
+
+ ret = -ERANGE;
+ if (stats.f_bsize <= 0)
+ goto error_unsupported;
+
+ ret = -EOPNOTSUPP;
+ if (stats.f_bsize > PAGE_SIZE)
+ goto error_unsupported;
+
+ cache->bsize = stats.f_bsize;
+ cache->bshift = 0;
+ if (stats.f_bsize < PAGE_SIZE)
+ cache->bshift = PAGE_SHIFT - ilog2(stats.f_bsize);
+
+ _debug("blksize %u (shift %u)",
+ cache->bsize, cache->bshift);
+
+ _debug("size %llu, avail %llu",
+ (unsigned long long) stats.f_blocks,
+ (unsigned long long) stats.f_bavail);
+
+ /* set up caching limits */
+ do_div(stats.f_files, 100);
+ cache->fstop = stats.f_files * cache->fstop_percent;
+ cache->fcull = stats.f_files * cache->fcull_percent;
+ cache->frun = stats.f_files * cache->frun_percent;
+
+ _debug("limits {%llu,%llu,%llu} files",
+ (unsigned long long) cache->frun,
+ (unsigned long long) cache->fcull,
+ (unsigned long long) cache->fstop);
+
+ stats.f_blocks >>= cache->bshift;
+ do_div(stats.f_blocks, 100);
+ cache->bstop = stats.f_blocks * cache->bstop_percent;
+ cache->bcull = stats.f_blocks * cache->bcull_percent;
+ cache->brun = stats.f_blocks * cache->brun_percent;
+
+ _debug("limits {%llu,%llu,%llu} blocks",
+ (unsigned long long) cache->brun,
+ (unsigned long long) cache->bcull,
+ (unsigned long long) cache->bstop);
+
+ /* get the cache directory and check its type */
+ cachedir = cachefiles_get_directory(cache, root, "cache");
+ if (IS_ERR(cachedir)) {
+ ret = PTR_ERR(cachedir);
+ goto error_unsupported;
+ }
+
+ fsdef->dentry = cachedir;
+
+ ret = cachefiles_check_object_type(fsdef);
+ if (ret < 0)
+ goto error_unsupported;
+
+ /* get the graveyard directory */
+ graveyard = cachefiles_get_directory(cache, root, "graveyard");
+ if (IS_ERR(graveyard)) {
+ ret = PTR_ERR(graveyard);
+ goto error_unsupported;
+ }
+
+ cache->graveyard = graveyard;
+
+ /* publish the cache */
+ fscache_init_cache(&cache->cache,
+ &cachefiles_cache_ops,
+ "%02x:%02x",
+ MAJOR(fsdef->dentry->d_sb->s_dev),
+ MINOR(fsdef->dentry->d_sb->s_dev));
+
+ ret = fscache_add_cache(&cache->cache, &fsdef->fscache, cache->tag);
+ if (ret < 0)
+ goto error_add_cache;
+
+ /* done */
+ set_bit(CACHEFILES_READY, &cache->flags);
+ dput(root);
+
+ printk(KERN_INFO "CacheFiles:"
+ " File cache on %s registered\n",
+ cache->cache.identifier);
+
+ /* check how much space the cache has */
+ cachefiles_has_space(cache, 0, 0);
+ cachefiles_end_secure(cache, saved_cred);
+ return 0;
+
+error_add_cache:
+ dput(cache->graveyard);
+ cache->graveyard = NULL;
+error_unsupported:
+ mntput(cache->mnt);
+ cache->mnt = NULL;
+ dput(fsdef->dentry);
+ fsdef->dentry = NULL;
+ dput(root);
+error_open_root:
+ kmem_cache_free(cachefiles_object_jar, fsdef);
+error_root_object:
+ cachefiles_end_secure(cache, saved_cred);
+ kerror("Failed to register: %d", ret);
+ return ret;
+}
+
+/*
+ * unbind a cache on fd release
+ */
+void cachefiles_daemon_unbind(struct cachefiles_cache *cache)
+{
+ _enter("");
+
+ if (test_bit(CACHEFILES_READY, &cache->flags)) {
+ printk(KERN_INFO "CacheFiles:"
+ " File cache on %s unregistering\n",
+ cache->cache.identifier);
+
+ fscache_withdraw_cache(&cache->cache);
+ }
+
+ if (cache->cache.fsdef)
+ cache->cache.ops->put_object(cache->cache.fsdef);
+
+ dput(cache->graveyard);
+ mntput(cache->mnt);
+
+ kfree(cache->rootdirname);
+ kfree(cache->tag);
+
+ _leave("");
+}
diff --git a/fs/cachefiles/cf-daemon.c b/fs/cachefiles/cf-daemon.c
new file mode 100644
index 0000000..fa91a20
--- /dev/null
+++ b/fs/cachefiles/cf-daemon.c
@@ -0,0 +1,720 @@
+/* Daemon interface
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/namei.h>
+#include <linux/poll.h>
+#include <linux/mount.h>
+#include <linux/statfs.h>
+#include <linux/ctype.h>
+#include "cf-internal.h"
+
+static int cachefiles_daemon_open(struct inode *, struct file *);
+static int cachefiles_daemon_release(struct inode *, struct file *);
+static ssize_t cachefiles_daemon_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t cachefiles_daemon_write(struct file *, const char __user *, size_t, loff_t *);
+static unsigned int cachefiles_daemon_poll(struct file *, struct poll_table_struct *);
+static int cachefiles_daemon_frun(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_fcull(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_fstop(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_brun(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_bcull(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_bstop(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_cull(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_debug(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_dir(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_tag(struct cachefiles_cache *cache, char *args);
+static int cachefiles_daemon_inuse(struct cachefiles_cache *cache, char *args);
+
+static unsigned long cachefiles_open;
+
+const struct file_operations cachefiles_daemon_fops = {
+ .owner = THIS_MODULE,
+ .open = cachefiles_daemon_open,
+ .release = cachefiles_daemon_release,
+ .read = cachefiles_daemon_read,
+ .write = cachefiles_daemon_write,
+ .poll = cachefiles_daemon_poll,
+};
+
+struct cachefiles_daemon_cmd {
+ char name[8];
+ int (*handler)(struct cachefiles_cache *cache, char *args);
+};
+
+static const struct cachefiles_daemon_cmd cachefiles_daemon_cmds[] = {
+ { "bind", cachefiles_daemon_bind },
+ { "brun", cachefiles_daemon_brun },
+ { "bcull", cachefiles_daemon_bcull },
+ { "bstop", cachefiles_daemon_bstop },
+ { "cull", cachefiles_daemon_cull },
+ { "debug", cachefiles_daemon_debug },
+ { "dir", cachefiles_daemon_dir },
+ { "frun", cachefiles_daemon_frun },
+ { "fcull", cachefiles_daemon_fcull },
+ { "fstop", cachefiles_daemon_fstop },
+ { "inuse", cachefiles_daemon_inuse },
+ { "tag", cachefiles_daemon_tag },
+ { "", NULL }
+};
+
+
+/*
+ * do various checks
+ */
+static int cachefiles_daemon_open(struct inode *inode, struct file *file)
+{
+ struct cachefiles_cache *cache;
+
+ _enter("");
+
+ /* only the superuser may do this */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ /* the cachefiles device may only be open once at a time */
+ if (xchg(&cachefiles_open, 1) == 1)
+ return -EBUSY;
+
+ /* allocate a cache record */
+ cache = kzalloc(sizeof(struct cachefiles_cache), GFP_KERNEL);
+ if (!cache) {
+ cachefiles_open = 0;
+ return -ENOMEM;
+ }
+
+ mutex_init(&cache->daemon_mutex);
+ cache->active_nodes = RB_ROOT;
+ rwlock_init(&cache->active_lock);
+ init_waitqueue_head(&cache->daemon_pollwq);
+
+ /* set default caching limits
+ * - limit at 1% free space and/or free files
+ * - cull below 5% free space and/or free files
+ * - cease culling above 7% free space and/or free files
+ */
+ cache->frun_percent = 7;
+ cache->fcull_percent = 5;
+ cache->fstop_percent = 1;
+ cache->brun_percent = 7;
+ cache->bcull_percent = 5;
+ cache->bstop_percent = 1;
+
+ file->private_data = cache;
+ cache->cachefilesd = file;
+ return 0;
+}
+
+/*
+ * release a cache
+ */
+static int cachefiles_daemon_release(struct inode *inode, struct file *file)
+{
+ struct cachefiles_cache *cache = file->private_data;
+
+ _enter("");
+
+ ASSERT(cache);
+
+ set_bit(CACHEFILES_DEAD, &cache->flags);
+
+ cachefiles_daemon_unbind(cache);
+
+ ASSERT(!cache->active_nodes.rb_node);
+
+ /* clean up the control file interface */
+ cache->cachefilesd = NULL;
+ file->private_data = NULL;
+ cachefiles_open = 0;
+
+ kfree(cache);
+
+ _leave("");
+ return 0;
+}
+
+/*
+ * read the cache state
+ */
+static ssize_t cachefiles_daemon_read(struct file *file, char __user *_buffer,
+ size_t buflen, loff_t *pos)
+{
+ struct cachefiles_cache *cache = file->private_data;
+ char buffer[256];
+ int n;
+
+ _enter(",,%zu,", buflen);
+
+ if (!test_bit(CACHEFILES_READY, &cache->flags))
+ return 0;
+
+ /* check how much space the cache has */
+ cachefiles_has_space(cache, 0, 0);
+
+ /* summarise */
+ clear_bit(CACHEFILES_STATE_CHANGED, &cache->flags);
+
+ n = snprintf(buffer, sizeof(buffer),
+ "cull=%c"
+ " frun=%llx"
+ " fcull=%llx"
+ " fstop=%llx"
+ " brun=%llx"
+ " bcull=%llx"
+ " bstop=%llx",
+ test_bit(CACHEFILES_CULLING, &cache->flags) ? '1' : '0',
+ (unsigned long long) cache->frun,
+ (unsigned long long) cache->fcull,
+ (unsigned long long) cache->fstop,
+ (unsigned long long) cache->brun,
+ (unsigned long long) cache->bcull,
+ (unsigned long long) cache->bstop
+ );
+
+ if (n > buflen)
+ return -EMSGSIZE;
+
+ if (copy_to_user(_buffer, buffer, n) != 0)
+ return -EFAULT;
+
+ return n;
+}
+
+/*
+ * command the cache
+ */
+static ssize_t cachefiles_daemon_write(struct file *file,
+ const char __user *_data,
+ size_t datalen,
+ loff_t *pos)
+{
+ const struct cachefiles_daemon_cmd *cmd;
+ struct cachefiles_cache *cache = file->private_data;
+ ssize_t ret;
+ char *data, *args, *cp;
+
+ _enter(",,%zu,", datalen);
+
+ ASSERT(cache);
+
+ if (test_bit(CACHEFILES_DEAD, &cache->flags))
+ return -EIO;
+
+ if (datalen < 0 || datalen > PAGE_SIZE - 1)
+ return -EOPNOTSUPP;
+
+ /* drag the command string into the kernel so we can parse it */
+ data = kmalloc(datalen + 1, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ ret = -EFAULT;
+ if (copy_from_user(data, _data, datalen) != 0)
+ goto error;
+
+ data[datalen] = '\0';
+
+ ret = -EINVAL;
+ if (memchr(data, '\0', datalen))
+ goto error;
+
+ /* strip any newline */
+ cp = memchr(data, '\n', datalen);
+ if (cp) {
+ if (cp == data)
+ goto error;
+
+ *cp = '\0';
+ }
+
+ /* parse the command */
+ ret = -EOPNOTSUPP;
+
+ for (args = data; *args; args++)
+ if (isspace(*args))
+ break;
+ if (*args) {
+ if (args == data)
+ goto error;
+ *args = '\0';
+ for (args++; isspace(*args); args++)
+ continue;
+ }
+
+ /* run the appropriate command handler */
+ for (cmd = cachefiles_daemon_cmds; cmd->name[0]; cmd++)
+ if (strcmp(cmd->name, data) == 0)
+ goto found_command;
+
+error:
+ kfree(data);
+ _leave(" = %zd", ret);
+ return ret;
+
+found_command:
+ mutex_lock(&cache->daemon_mutex);
+
+ ret = -EIO;
+ if (!test_bit(CACHEFILES_DEAD, &cache->flags))
+ ret = cmd->handler(cache, args);
+
+ mutex_unlock(&cache->daemon_mutex);
+
+ if (ret == 0)
+ ret = datalen;
+ goto error;
+}
+
+/*
+ * poll for culling state
+ * - use POLLOUT to indicate culling state
+ */
+static unsigned int cachefiles_daemon_poll(struct file *file,
+ struct poll_table_struct *poll)
+{
+ struct cachefiles_cache *cache = file->private_data;
+ unsigned int mask;
+
+ poll_wait(file, &cache->daemon_pollwq, poll);
+ mask = 0;
+
+ if (test_bit(CACHEFILES_STATE_CHANGED, &cache->flags))
+ mask |= POLLIN;
+
+ if (test_bit(CACHEFILES_CULLING, &cache->flags))
+ mask |= POLLOUT;
+
+ return mask;
+}
+
+/*
+ * give a range error for cache space constraints
+ * - can be tail-called
+ */
+static int cachefiles_daemon_range_error(struct cachefiles_cache *cache, char *args)
+{
+ kerror("Free space limits must be in range"
+ " 0%%<=stop<cull<run<100%%");
+
+ return -EINVAL;
+}
+
+/*
+ * set the percentage of files at which to stop culling
+ * - command: "frun <N>%"
+ */
+static int cachefiles_daemon_frun(struct cachefiles_cache *cache, char *args)
+{
+ unsigned long frun;
+
+ _enter(",%s", args);
+
+ if (!*args)
+ return -EINVAL;
+
+ frun = simple_strtoul(args, &args, 10);
+ if (args[0] != '%' || args[1] != '\0')
+ return -EINVAL;
+
+ if (frun <= cache->fcull_percent || frun >= 100)
+ return cachefiles_daemon_range_error(cache, args);
+
+ cache->frun_percent = frun;
+ return 0;
+}
+
+/*
+ * set the percentage of files at which to start culling
+ * - command: "fcull <N>%"
+ */
+static int cachefiles_daemon_fcull(struct cachefiles_cache *cache, char *args)
+{
+ unsigned long fcull;
+
+ _enter(",%s", args);
+
+ if (!*args)
+ return -EINVAL;
+
+ fcull = simple_strtoul(args, &args, 10);
+ if (args[0] != '%' || args[1] != '\0')
+ return -EINVAL;
+
+ if (fcull <= cache->fstop_percent || fcull >= cache->frun_percent)
+ return cachefiles_daemon_range_error(cache, args);
+
+ cache->fcull_percent = fcull;
+ return 0;
+}
+
+/*
+ * set the percentage of files at which to stop allocating
+ * - command: "fstop <N>%"
+ */
+static int cachefiles_daemon_fstop(struct cachefiles_cache *cache, char *args)
+{
+ unsigned long fstop;
+
+ _enter(",%s", args);
+
+ if (!*args)
+ return -EINVAL;
+
+ fstop = simple_strtoul(args, &args, 10);
+ if (args[0] != '%' || args[1] != '\0')
+ return -EINVAL;
+
+ if (fstop < 0 || fstop >= cache->fcull_percent)
+ return cachefiles_daemon_range_error(cache, args);
+
+ cache->fstop_percent = fstop;
+ return 0;
+}
+
+/*
+ * set the percentage of blocks at which to stop culling
+ * - command: "brun <N>%"
+ */
+static int cachefiles_daemon_brun(struct cachefiles_cache *cache, char *args)
+{
+ unsigned long brun;
+
+ _enter(",%s", args);
+
+ if (!*args)
+ return -EINVAL;
+
+ brun = simple_strtoul(args, &args, 10);
+ if (args[0] != '%' || args[1] != '\0')
+ return -EINVAL;
+
+ if (brun <= cache->bcull_percent || brun >= 100)
+ return cachefiles_daemon_range_error(cache, args);
+
+ cache->brun_percent = brun;
+ return 0;
+}
+
+/*
+ * set the percentage of blocks at which to start culling
+ * - command: "bcull <N>%"
+ */
+static int cachefiles_daemon_bcull(struct cachefiles_cache *cache, char *args)
+{
+ unsigned long bcull;
+
+ _enter(",%s", args);
+
+ if (!*args)
+ return -EINVAL;
+
+ bcull = simple_strtoul(args, &args, 10);
+ if (args[0] != '%' || args[1] != '\0')
+ return -EINVAL;
+
+ if (bcull <= cache->bstop_percent || bcull >= cache->brun_percent)
+ return cachefiles_daemon_range_error(cache, args);
+
+ cache->bcull_percent = bcull;
+ return 0;
+}
+
+/*
+ * set the percentage of blocks at which to stop allocating
+ * - command: "bstop <N>%"
+ */
+static int cachefiles_daemon_bstop(struct cachefiles_cache *cache, char *args)
+{
+ unsigned long bstop;
+
+ _enter(",%s", args);
+
+ if (!*args)
+ return -EINVAL;
+
+ bstop = simple_strtoul(args, &args, 10);
+ if (args[0] != '%' || args[1] != '\0')
+ return -EINVAL;
+
+ if (bstop < 0 || bstop >= cache->bcull_percent)
+ return cachefiles_daemon_range_error(cache, args);
+
+ cache->bstop_percent = bstop;
+ return 0;
+}
+
+/*
+ * set the cache directory
+ * - command: "dir <name>"
+ */
+static int cachefiles_daemon_dir(struct cachefiles_cache *cache, char *args)
+{
+ char *dir;
+
+ _enter(",%s", args);
+
+ if (!*args) {
+ kerror("Empty directory specified");
+ return -EINVAL;
+ }
+
+ if (cache->rootdirname) {
+ kerror("Second cache directory specified");
+ return -EEXIST;
+ }
+
+ dir = kstrdup(args, GFP_KERNEL);
+ if (!dir)
+ return -ENOMEM;
+
+ cache->rootdirname = dir;
+ return 0;
+}
+
+/*
+ * set the cache tag
+ * - command: "tag <name>"
+ */
+static int cachefiles_daemon_tag(struct cachefiles_cache *cache, char *args)
+{
+ char *tag;
+
+ _enter(",%s", args);
+
+ if (!*args) {
+ kerror("Empty tag specified");
+ return -EINVAL;
+ }
+
+ if (cache->tag)
+ return -EEXIST;
+
+ tag = kstrdup(args, GFP_KERNEL);
+ if (!tag)
+ return -ENOMEM;
+
+ cache->tag = tag;
+ return 0;
+}
+
+/*
+ * request a node in the cache be culled from the current working directory
+ * - command: "cull <name>"
+ */
+static int cachefiles_daemon_cull(struct cachefiles_cache *cache, char *args)
+{
+ struct fs_struct *fs;
+ struct dentry *dir;
+ struct cred *saved_cred;
+ int ret;
+
+ _enter(",%s", args);
+
+ if (strchr(args, '/'))
+ goto inval;
+
+ if (!test_bit(CACHEFILES_READY, &cache->flags)) {
+ kerror("cull applied to unready cache");
+ return -EIO;
+ }
+
+ if (test_bit(CACHEFILES_DEAD, &cache->flags)) {
+ kerror("cull applied to dead cache");
+ return -EIO;
+ }
+
+ /* extract the directory dentry from the cwd */
+ fs = current->fs;
+ read_lock(&fs->lock);
+ dir = dget(fs->pwd);
+ read_unlock(&fs->lock);
+
+ if (!S_ISDIR(dir->d_inode->i_mode))
+ goto notdir;
+
+ cachefiles_begin_secure(cache, &saved_cred);
+ ret = cachefiles_cull(cache, dir, args);
+ cachefiles_end_secure(cache, saved_cred);
+
+ dput(dir);
+ _leave(" = %d", ret);
+ return ret;
+
+notdir:
+ dput(dir);
+ kerror("cull command requires dirfd to be a directory");
+ return -ENOTDIR;
+
+inval:
+ kerror("cull command requires dirfd and filename");
+ return -EINVAL;
+}
+
+/*
+ * set debugging mode
+ * - command: "debug <mask>"
+ */
+static int cachefiles_daemon_debug(struct cachefiles_cache *cache, char *args)
+{
+ unsigned long mask;
+
+ _enter(",%s", args);
+
+ mask = simple_strtoul(args, &args, 0);
+ if (args[0] != '\0')
+ goto inval;
+
+ cachefiles_debug = mask;
+ _leave(" = 0");
+ return 0;
+
+inval:
+ kerror("debug command requires mask");
+ return -EINVAL;
+}
+
+/*
+ * find out whether an object in the current working directory is in use or not
+ * - command: "inuse <name>"
+ */
+static int cachefiles_daemon_inuse(struct cachefiles_cache *cache, char *args)
+{
+ struct fs_struct *fs;
+ struct dentry *dir;
+ struct cred *saved_cred;
+ int ret;
+
+ _enter(",%s", args);
+
+ if (strchr(args, '/'))
+ goto inval;
+
+ if (!test_bit(CACHEFILES_READY, &cache->flags)) {
+ kerror("inuse applied to unready cache");
+ return -EIO;
+ }
+
+ if (test_bit(CACHEFILES_DEAD, &cache->flags)) {
+ kerror("inuse applied to dead cache");
+ return -EIO;
+ }
+
+ /* extract the directory dentry from the cwd */
+ fs = current->fs;
+ read_lock(&fs->lock);
+ dir = dget(fs->pwd);
+ read_unlock(&fs->lock);
+
+ if (!S_ISDIR(dir->d_inode->i_mode))
+ goto notdir;
+
+ cachefiles_begin_secure(cache, &saved_cred);
+ ret = cachefiles_check_in_use(cache, dir, args);
+ cachefiles_end_secure(cache, saved_cred);
+
+ dput(dir);
+ _leave(" = %d", ret);
+ return ret;
+
+notdir:
+ dput(dir);
+ kerror("inuse command requires dirfd to be a directory");
+ return -ENOTDIR;
+
+inval:
+ kerror("inuse command requires dirfd and filename");
+ return -EINVAL;
+}
+
+/*
+ * see if we have space for a number of pages and/or a number of files in the
+ * cache
+ */
+int cachefiles_has_space(struct cachefiles_cache *cache,
+ unsigned fnr, unsigned bnr)
+{
+ struct kstatfs stats;
+ int ret;
+
+ _enter("{%llu,%llu,%llu,%llu,%llu,%llu},%u,%u",
+ (unsigned long long) cache->frun,
+ (unsigned long long) cache->fcull,
+ (unsigned long long) cache->fstop,
+ (unsigned long long) cache->brun,
+ (unsigned long long) cache->bcull,
+ (unsigned long long) cache->bstop,
+ fnr, bnr);
+
+ /* find out how many pages of blockdev are available */
+ memset(&stats, 0, sizeof(stats));
+
+ ret = vfs_statfs(cache->mnt->mnt_root, &stats);
+ if (ret < 0) {
+ if (ret == -EIO)
+ cachefiles_io_error(cache, "statfs failed");
+ _leave(" = %d", ret);
+ return ret;
+ }
+
+ stats.f_bavail >>= cache->bshift;
+
+ _debug("avail %llu,%llu",
+ (unsigned long long) stats.f_ffree,
+ (unsigned long long) stats.f_bavail);
+
+ /* see if there is sufficient space */
+ if (stats.f_ffree > fnr)
+ stats.f_ffree -= fnr;
+ else
+ stats.f_ffree = 0;
+
+ if (stats.f_bavail > bnr)
+ stats.f_bavail -= bnr;
+ else
+ stats.f_bavail = 0;
+
+ ret = -ENOBUFS;
+ if (stats.f_ffree < cache->fstop ||
+ stats.f_bavail < cache->bstop)
+ goto begin_cull;
+
+ ret = 0;
+ if (stats.f_ffree < cache->fcull ||
+ stats.f_bavail < cache->bcull)
+ goto begin_cull;
+
+ if (test_bit(CACHEFILES_CULLING, &cache->flags) &&
+ stats.f_ffree >= cache->frun &&
+ stats.f_bavail >= cache->brun &&
+ test_and_clear_bit(CACHEFILES_CULLING, &cache->flags)
+ ) {
+ _debug("cease culling");
+ cachefiles_state_changed(cache);
+ }
+
+ _leave(" = 0");
+ return 0;
+
+begin_cull:
+ if (!test_and_set_bit(CACHEFILES_CULLING, &cache->flags)) {
+ _debug("### CULL CACHE ###");
+ cachefiles_state_changed(cache);
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+}
diff --git a/fs/cachefiles/cf-interface.c b/fs/cachefiles/cf-interface.c
new file mode 100644
index 0000000..ed18c14
--- /dev/null
+++ b/fs/cachefiles/cf-interface.c
@@ -0,0 +1,442 @@
+/* FS-Cache interface to CacheFiles
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/mount.h>
+#include <linux/buffer_head.h>
+#include "cf-internal.h"
+
+#define list_to_page(head) (list_entry((head)->prev, struct page, lru))
+
+struct cachefiles_lookup_data {
+ struct cachefiles_xattr *auxdata; /* auxiliary data */
+ char *key; /* key path */
+};
+
+static int cachefiles_attr_changed(struct fscache_object *_object);
+
+/*
+ * allocate an object record for a cookie lookup and prepare the lookup data
+ */
+static struct fscache_object *cachefiles_alloc_object(
+ struct fscache_cache *_cache,
+ struct fscache_cookie *cookie)
+{
+ struct cachefiles_lookup_data *lookup_data;
+ struct cachefiles_object *object;
+ struct cachefiles_cache *cache;
+ struct cachefiles_xattr *auxdata;
+ unsigned keylen, auxlen;
+ void *buffer;
+ char *key;
+
+ cache = container_of(_cache, struct cachefiles_cache, cache);
+
+ _enter("{%s},%p,", cache->cache.identifier, cookie);
+
+ lookup_data = kmalloc(sizeof(lookup_data), GFP_KERNEL);
+ if (!lookup_data)
+ goto nomem_lookup_data;
+
+ /* create a new object record and a temporary leaf image */
+ object = kmem_cache_alloc(cachefiles_object_jar, GFP_KERNEL);
+ if (!object)
+ goto nomem_object;
+
+ ASSERTCMP(object->backer, ==, NULL);
+
+ BUG_ON(test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags));
+ atomic_set(&object->usage, 1);
+
+ fscache_object_init(&object->fscache);
+ object->fscache.cookie = cookie;
+ object->fscache.cache = &cache->cache;
+
+ object->type = cookie->def->type;
+
+ /* get hold of the raw key
+ * - stick the length on the front and leave space on the back for the
+ * encoder
+ */
+ buffer = kmalloc((2 + 512) + 3, GFP_KERNEL);
+ if (!buffer)
+ goto nomem_buffer;
+
+ keylen = cookie->def->get_key(cookie->netfs_data, buffer + 2, 512);
+ ASSERTCMP(keylen, <, 512);
+
+ *(uint16_t *)buffer = keylen;
+ ((char *)buffer)[keylen + 2] = 0;
+ ((char *)buffer)[keylen + 3] = 0;
+ ((char *)buffer)[keylen + 4] = 0;
+
+ /* turn the raw key into something that can work with as a filename */
+ key = cachefiles_cook_key(buffer, keylen + 2, object->type);
+ if (!key)
+ goto nomem_key;
+
+ /* get hold of the auxiliary data and prepend the object type */
+ auxdata = buffer;
+ auxlen = 0;
+ if (cookie->def->get_aux) {
+ auxlen = cookie->def->get_aux(cookie->netfs_data,
+ auxdata->data, 511);
+ ASSERTCMP(auxlen, <, 511);
+ }
+
+ auxdata->len = auxlen + 1;
+ auxdata->type = cookie->def->type;
+
+ lookup_data->auxdata = auxdata;
+ lookup_data->key = key;
+ object->lookup_data = lookup_data;
+
+ _leave(" = %p [%p]", &object->fscache, lookup_data);
+ return &object->fscache;
+
+nomem_key:
+ kfree(buffer);
+nomem_buffer:
+ BUG_ON(test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags));
+ kmem_cache_free(cachefiles_object_jar, object);
+nomem_object:
+ kfree(lookup_data);
+nomem_lookup_data:
+ _leave(" = -ENOMEM");
+ return ERR_PTR(-ENOMEM);
+}
+
+/*
+ * attempt to look up the nominated node in this cache
+ */
+static void cachefiles_lookup_object(struct fscache_object *_object)
+{
+ struct cachefiles_lookup_data *lookup_data;
+ struct cachefiles_object *parent, *object;
+ struct cachefiles_cache *cache;
+ struct cred *saved_cred;
+ int ret;
+
+ _enter("{OBJ%x}", _object->debug_id);
+
+ cache = container_of(_object->cache, struct cachefiles_cache, cache);
+ parent = container_of(_object->parent,
+ struct cachefiles_object, fscache);
+ object = container_of(_object, struct cachefiles_object, fscache);
+ lookup_data = object->lookup_data;
+
+ ASSERTCMP(lookup_data, !=, NULL);
+
+ /* look up the key, creating any missing bits */
+ cachefiles_begin_secure(cache, &saved_cred);
+ ret = cachefiles_walk_to_object(parent, object,
+ lookup_data->key,
+ lookup_data->auxdata);
+ cachefiles_end_secure(cache, saved_cred);
+
+ /* polish off by setting the attributes of non-index files */
+ if (ret == 0 &&
+ object->fscache.cookie->def->type != FSCACHE_COOKIE_TYPE_INDEX)
+ cachefiles_attr_changed(&object->fscache);
+
+ if (ret < 0)
+ fscache_object_lookup_error(&object->fscache);
+
+ _leave(" [%d]", ret);
+}
+
+/*
+ * indication of lookup completion
+ */
+static void cachefiles_lookup_complete(struct fscache_object *_object)
+{
+ struct cachefiles_object *object;
+
+ object = container_of(_object, struct cachefiles_object, fscache);
+
+ _enter("{OBJ%x,%p}", object->fscache.debug_id, object->lookup_data);
+
+ if (object->lookup_data) {
+ kfree(object->lookup_data->key);
+ kfree(object->lookup_data->auxdata);
+ kfree(object->lookup_data);
+ object->lookup_data = NULL;
+ }
+}
+
+/*
+ * increment the usage count on an inode object (may fail if unmounting)
+ */
+static struct fscache_object *cachefiles_grab_object(struct fscache_object *_object)
+{
+ struct cachefiles_object *object;
+
+ _enter("{OBJ%x}", _object->debug_id);
+
+ object = container_of(_object, struct cachefiles_object, fscache);
+
+#ifdef CACHEFILES_DEBUG_SLAB
+ ASSERT((atomic_read(&object->usage) & 0xffff0000) != 0x6b6b0000);
+#endif
+
+ atomic_inc(&object->usage);
+ return &object->fscache;
+}
+
+/*
+ * update the auxilliary data for an object object on disk
+ */
+static void cachefiles_update_object(struct fscache_object *_object)
+{
+ struct cachefiles_object *object;
+ struct cachefiles_xattr *auxdata;
+ struct cachefiles_cache *cache;
+ struct fscache_cookie *cookie;
+ struct cred *saved_cred;
+ unsigned auxlen;
+
+ _enter("{OBJ%x}", _object->debug_id);
+
+ object = container_of(_object, struct cachefiles_object, fscache);
+ cache = container_of(object->fscache.cache, struct cachefiles_cache,
+ cache);
+ cookie = object->fscache.cookie;
+
+ if (!cookie->def->get_aux) {
+ _leave(" [no aux]");
+ return;
+ }
+
+ auxdata = kmalloc(2 + 512 + 3, GFP_KERNEL);
+ if (!auxdata) {
+ _leave(" [nomem]");
+ return;
+ }
+
+ auxlen = cookie->def->get_aux(cookie->netfs_data, auxdata->data, 511);
+ ASSERTCMP(auxlen, <, 511);
+
+ auxdata->len = auxlen + 1;
+ auxdata->type = cookie->def->type;
+
+ cachefiles_begin_secure(cache, &saved_cred);
+ cachefiles_update_object_xattr(object, auxdata);
+ cachefiles_end_secure(cache, saved_cred);
+ kfree(auxdata);
+ _leave("");
+}
+
+/*
+ * discard the resources pinned by an object and effect retirement if
+ * requested
+ */
+static void cachefiles_drop_object(struct fscache_object *_object)
+{
+ struct cachefiles_object *object;
+ struct cachefiles_cache *cache;
+ struct cred *saved_cred;
+
+ ASSERT(_object);
+
+ object = container_of(_object, struct cachefiles_object, fscache);
+
+ _enter("{OBJ%x,%d}",
+ object->fscache.debug_id, atomic_read(&object->usage));
+
+ cache = container_of(object->fscache.cache,
+ struct cachefiles_cache, cache);
+
+#ifdef CACHEFILES_DEBUG_SLAB
+ ASSERT((atomic_read(&object->usage) & 0xffff0000) != 0x6b6b0000);
+#endif
+
+ /* delete retired objects */
+ if (object->fscache.state == FSCACHE_OBJECT_RECYCLING &&
+ _object != cache->cache.fsdef
+ ) {
+ _debug("- retire object OBJ%x", object->fscache.debug_id);
+ cachefiles_begin_secure(cache, &saved_cred);
+ cachefiles_delete_object(cache, object);
+ cachefiles_end_secure(cache, saved_cred);
+ }
+
+ /* close the filesystem stuff attached to the object */
+ if (object->backer != object->dentry)
+ dput(object->backer);
+ object->backer = NULL;
+
+ /* note that the object is now inactive */
+ if (test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags)) {
+ write_lock(&cache->active_lock);
+ if (!test_and_clear_bit(CACHEFILES_OBJECT_ACTIVE,
+ &object->flags))
+ BUG();
+ rb_erase(&object->active_node, &cache->active_nodes);
+ wake_up_bit(&object->flags, CACHEFILES_OBJECT_ACTIVE);
+ write_unlock(&cache->active_lock);
+ }
+
+ dput(object->dentry);
+ object->dentry = NULL;
+
+ _leave("");
+}
+
+/*
+ * dispose of a reference to an object
+ */
+static void cachefiles_put_object(struct fscache_object *_object)
+{
+ struct cachefiles_object *object;
+
+ ASSERT(_object);
+
+ object = container_of(_object, struct cachefiles_object, fscache);
+
+ _enter("{OBJ%x,%d}",
+ object->fscache.debug_id, atomic_read(&object->usage));
+
+#ifdef CACHEFILES_DEBUG_SLAB
+ ASSERT((atomic_read(&object->usage) & 0xffff0000) != 0x6b6b0000);
+#endif
+
+ ASSERTIFCMP(object->fscache.parent,
+ object->fscache.parent->n_children, >, 0);
+
+ if (atomic_dec_and_test(&object->usage)) {
+ _debug("- kill object OBJ%x", object->fscache.debug_id);
+
+ ASSERT(!test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags));
+ ASSERTCMP(object->fscache.parent, ==, NULL);
+ ASSERTCMP(object->backer, ==, NULL);
+ ASSERTCMP(object->dentry, ==, NULL);
+ ASSERTCMP(object->fscache.n_ops, ==, 0);
+ ASSERTCMP(object->fscache.n_children, ==, 0);
+
+ if (object->lookup_data) {
+ kfree(object->lookup_data->key);
+ kfree(object->lookup_data->auxdata);
+ kfree(object->lookup_data);
+ object->lookup_data = NULL;
+ }
+
+ kmem_cache_free(cachefiles_object_jar, object);
+ }
+
+ _leave("");
+}
+
+/*
+ * sync a cache
+ */
+static void cachefiles_sync_cache(struct fscache_cache *_cache)
+{
+ struct cachefiles_cache *cache;
+ struct cred *saved_cred;
+ int ret;
+
+ _enter("%p", _cache);
+
+ cache = container_of(_cache, struct cachefiles_cache, cache);
+
+ /* make sure all pages pinned by operations on behalf of the netfs are
+ * written to disc */
+ cachefiles_begin_secure(cache, &saved_cred);
+ ret = fsync_super(cache->mnt->mnt_sb);
+ cachefiles_end_secure(cache, saved_cred);
+
+ if (ret == -EIO)
+ cachefiles_io_error(cache,
+ "Attempt to sync backing fs superblock"
+ " returned error %d",
+ ret);
+}
+
+/*
+ * notification the attributes on an object have changed
+ * - called with reads/writes excluded by FS-Cache
+ */
+static int cachefiles_attr_changed(struct fscache_object *_object)
+{
+ struct cachefiles_object *object;
+ struct cachefiles_cache *cache;
+ struct iattr newattrs;
+ struct cred *saved_cred;
+ loff_t ni_size, oi_size;
+ int ret;
+
+ _object->cookie->def->get_attr(_object->cookie->netfs_data, &ni_size);
+
+ _enter("{OBJ%x},[%llu]", _object->debug_id, ni_size);
+
+ object = container_of(_object, struct cachefiles_object, fscache);
+ cache = container_of(object->fscache.cache,
+ struct cachefiles_cache, cache);
+
+ if (ni_size == object->i_size)
+ return 0;
+
+ if (!object->backer)
+ return -ENOBUFS;
+
+ ASSERT(S_ISREG(object->backer->d_inode->i_mode));
+
+ fscache_set_store_limit(&object->fscache, ni_size);
+
+ oi_size = i_size_read(object->backer->d_inode);
+ if (oi_size == ni_size)
+ return 0;
+
+ newattrs.ia_size = ni_size;
+ newattrs.ia_valid = ATTR_SIZE;
+
+ cachefiles_begin_secure(cache, &saved_cred);
+ mutex_lock(&object->backer->d_inode->i_mutex);
+ ret = notify_change(object->backer, &newattrs);
+ mutex_unlock(&object->backer->d_inode->i_mutex);
+ cachefiles_end_secure(cache, saved_cred);
+
+ if (ret == -EIO) {
+ fscache_set_store_limit(&object->fscache, 0);
+ cachefiles_io_error_obj(object, "Size set failed");
+ ret = -ENOBUFS;
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * dissociate a cache from all the pages it was backing
+ */
+static void cachefiles_dissociate_pages(struct fscache_cache *cache)
+{
+ _enter("");
+}
+
+const struct fscache_cache_ops cachefiles_cache_ops = {
+ .name = "cachefiles",
+ .alloc_object = cachefiles_alloc_object,
+ .lookup_object = cachefiles_lookup_object,
+ .lookup_complete = cachefiles_lookup_complete,
+ .grab_object = cachefiles_grab_object,
+ .update_object = cachefiles_update_object,
+ .drop_object = cachefiles_drop_object,
+ .put_object = cachefiles_put_object,
+ .sync_cache = cachefiles_sync_cache,
+ .attr_changed = cachefiles_attr_changed,
+ .read_or_alloc_page = cachefiles_read_or_alloc_page,
+ .read_or_alloc_pages = cachefiles_read_or_alloc_pages,
+ .allocate_page = cachefiles_allocate_page,
+ .allocate_pages = cachefiles_allocate_pages,
+ .write_page = cachefiles_write_page,
+ .uncache_page = cachefiles_uncache_page,
+ .dissociate_pages = cachefiles_dissociate_pages,
+};
diff --git a/fs/cachefiles/cf-internal.h b/fs/cachefiles/cf-internal.h
new file mode 100644
index 0000000..e62af5d
--- /dev/null
+++ b/fs/cachefiles/cf-internal.h
@@ -0,0 +1,369 @@
+/* General netfs cache on cache files internal defs
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/fscache-cache.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/security.h>
+
+struct cachefiles_cache;
+struct cachefiles_object;
+
+extern unsigned cachefiles_debug;
+#define CACHEFILES_DEBUG_KENTER 1
+#define CACHEFILES_DEBUG_KLEAVE 2
+#define CACHEFILES_DEBUG_KDEBUG 4
+
+/*
+ * node records
+ */
+struct cachefiles_object {
+ struct fscache_object fscache; /* fscache handle */
+ struct cachefiles_lookup_data *lookup_data; /* cached lookup data */
+ struct dentry *dentry; /* the file/dir representing this object */
+ struct dentry *backer; /* backing file */
+ loff_t i_size; /* object size */
+ unsigned long flags;
+#define CACHEFILES_OBJECT_ACTIVE 0 /* T if marked active */
+ atomic_t usage; /* object usage count */
+ uint8_t type; /* object type */
+ uint8_t new; /* T if object new */
+ spinlock_t work_lock;
+ struct rb_node active_node; /* link in active tree (dentry is key) */
+};
+
+extern struct kmem_cache *cachefiles_object_jar;
+
+/*
+ * Cache files cache definition
+ */
+struct cachefiles_cache {
+ struct fscache_cache cache; /* FS-Cache record */
+ struct vfsmount *mnt; /* mountpoint holding the cache */
+ struct dentry *graveyard; /* directory into which dead objects go */
+ struct file *cachefilesd; /* manager daemon handle */
+ struct cred *cache_cred; /* credentials for accessing cache */
+ struct mutex daemon_mutex; /* command serialisation mutex */
+ wait_queue_head_t daemon_pollwq; /* poll waitqueue for daemon */
+ struct rb_root active_nodes; /* active nodes (can't be culled) */
+ rwlock_t active_lock; /* lock for active_nodes */
+ atomic_t gravecounter; /* graveyard uniquifier */
+ unsigned frun_percent; /* when to stop culling (% files) */
+ unsigned fcull_percent; /* when to start culling (% files) */
+ unsigned fstop_percent; /* when to stop allocating (% files) */
+ unsigned brun_percent; /* when to stop culling (% blocks) */
+ unsigned bcull_percent; /* when to start culling (% blocks) */
+ unsigned bstop_percent; /* when to stop allocating (% blocks) */
+ unsigned bsize; /* cache's block size */
+ unsigned bshift; /* min(ilog2(PAGE_SIZE / bsize), 0) */
+ uint64_t frun; /* when to stop culling */
+ uint64_t fcull; /* when to start culling */
+ uint64_t fstop; /* when to stop allocating */
+ sector_t brun; /* when to stop culling */
+ sector_t bcull; /* when to start culling */
+ sector_t bstop; /* when to stop allocating */
+ unsigned long flags;
+#define CACHEFILES_READY 0 /* T if cache prepared */
+#define CACHEFILES_DEAD 1 /* T if cache dead */
+#define CACHEFILES_CULLING 2 /* T if cull engaged */
+#define CACHEFILES_STATE_CHANGED 3 /* T if state changed (poll trigger) */
+ char *rootdirname; /* name of cache root directory */
+ char *tag; /* cache binding tag */
+};
+
+/*
+ * backing file read tracking
+ */
+struct cachefiles_one_read {
+ wait_queue_t monitor; /* link into monitored waitqueue */
+ struct page *back_page; /* backing file page we're waiting for */
+ struct page *netfs_page; /* netfs page we're going to fill */
+ struct fscache_retrieval *op; /* retrieval op covering this */
+ struct list_head op_link; /* link in op's todo list */
+};
+
+/*
+ * backing file write tracking
+ */
+struct cachefiles_one_write {
+ struct page *netfs_page; /* netfs page to copy */
+ struct cachefiles_object *object;
+ struct list_head obj_link; /* link in object's lists */
+ fscache_rw_complete_t end_io_func;
+ void *context;
+};
+
+/*
+ * auxiliary data xattr buffer
+ */
+struct cachefiles_xattr {
+ uint16_t len;
+ uint8_t type;
+ uint8_t data[];
+};
+
+/*
+ * note change of state for daemon
+ */
+static inline void cachefiles_state_changed(struct cachefiles_cache *cache)
+{
+ set_bit(CACHEFILES_STATE_CHANGED, &cache->flags);
+ wake_up_all(&cache->daemon_pollwq);
+}
+
+/*
+ * cf-bind.c
+ */
+extern int cachefiles_daemon_bind(struct cachefiles_cache *cache, char *args);
+extern void cachefiles_daemon_unbind(struct cachefiles_cache *cache);
+
+/*
+ * cf-daemon.c
+ */
+extern const struct file_operations cachefiles_daemon_fops;
+
+extern int cachefiles_has_space(struct cachefiles_cache *cache,
+ unsigned fnr, unsigned bnr);
+
+/*
+ * cf-interface.c
+ */
+extern const struct fscache_cache_ops cachefiles_cache_ops;
+
+/*
+ * cf-key.c
+ */
+extern char *cachefiles_cook_key(const u8 *raw, int keylen, uint8_t type);
+
+/*
+ * cf-namei.c
+ */
+extern int cachefiles_delete_object(struct cachefiles_cache *cache,
+ struct cachefiles_object *object);
+extern int cachefiles_walk_to_object(struct cachefiles_object *parent,
+ struct cachefiles_object *object,
+ const char *key,
+ struct cachefiles_xattr *auxdata);
+extern struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
+ struct dentry *dir,
+ const char *name);
+
+extern int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
+ char *filename);
+
+extern int cachefiles_check_in_use(struct cachefiles_cache *cache,
+ struct dentry *dir, char *filename);
+
+/*
+ * cf-proc.c
+ */
+#ifdef CONFIG_CACHEFILES_HISTOGRAM
+extern atomic_t cachefiles_lookup_histogram[HZ];
+extern atomic_t cachefiles_mkdir_histogram[HZ];
+extern atomic_t cachefiles_create_histogram[HZ];
+
+extern int __init cachefiles_proc_init(void);
+extern void cachefiles_proc_cleanup(void);
+static inline
+void cachefiles_hist(atomic_t histogram[], unsigned long start_jif)
+{
+ unsigned long jif = jiffies - start_jif;
+ if (jif >= HZ)
+ jif = HZ - 1;
+ atomic_inc(&histogram[jif]);
+}
+
+#else
+#define cachefiles_proc_init() (0)
+#define cachefiles_proc_cleanup() do {} while(0)
+#define cachefiles_hist(hist, start_jif) do {} while(0)
+#endif
+
+/*
+ * cf-rdwr.c
+ */
+extern int cachefiles_read_or_alloc_page(struct fscache_retrieval *,
+ struct page *, gfp_t);
+extern int cachefiles_read_or_alloc_pages(struct fscache_retrieval *,
+ struct list_head *, unsigned *,
+ gfp_t);
+extern int cachefiles_allocate_page(struct fscache_retrieval *, struct page *,
+ gfp_t);
+extern int cachefiles_allocate_pages(struct fscache_retrieval *,
+ struct list_head *, unsigned *, gfp_t);
+extern int cachefiles_write_page(struct fscache_storage *, struct page *);
+extern void cachefiles_uncache_page(struct fscache_object *, struct page *);
+
+/*
+ * cf-security.c
+ */
+extern int cachefiles_get_security_ID(struct cachefiles_cache *cache);
+extern int cachefiles_determine_cache_secid(struct cachefiles_cache *cache,
+ struct dentry *root);
+
+static inline void cachefiles_begin_secure(struct cachefiles_cache *cache,
+ struct cred **_saved_cred)
+{
+ *_saved_cred = __set_current_cred(get_cred(cache->cache_cred));
+}
+
+static inline void cachefiles_end_secure(struct cachefiles_cache *cache,
+ struct cred *saved_cred)
+{
+ set_current_cred(saved_cred);
+}
+
+/*
+ * cf-xattr.c
+ */
+extern int cachefiles_check_object_type(struct cachefiles_object *object);
+extern int cachefiles_set_object_xattr(struct cachefiles_object *object,
+ struct cachefiles_xattr *auxdata);
+extern int cachefiles_update_object_xattr(struct cachefiles_object *object,
+ struct cachefiles_xattr *auxdata);
+extern int cachefiles_check_object_xattr(struct cachefiles_object *object,
+ struct cachefiles_xattr *auxdata);
+extern int cachefiles_remove_object_xattr(struct cachefiles_cache *cache,
+ struct dentry *dentry);
+
+
+/*
+ * error handling
+ */
+#define kerror(FMT,...) printk(KERN_ERR "CacheFiles: "FMT"\n" ,##__VA_ARGS__);
+
+#define cachefiles_io_error(___cache, FMT, ...) \
+do { \
+ kerror("I/O Error: " FMT ,##__VA_ARGS__); \
+ fscache_io_error(&(___cache)->cache); \
+ set_bit(CACHEFILES_DEAD, &(___cache)->flags); \
+} while(0)
+
+#define cachefiles_io_error_obj(object, FMT, ...) \
+do { \
+ struct cachefiles_cache *___cache; \
+ \
+ ___cache = container_of((object)->fscache.cache, \
+ struct cachefiles_cache, cache); \
+ cachefiles_io_error(___cache, FMT ,##__VA_ARGS__); \
+} while(0)
+
+
+/*
+ * debug tracing
+ */
+#define dbgprintk(FMT,...) \
+ printk("[%-6.6s] "FMT"\n",current->comm ,##__VA_ARGS__)
+
+/* make sure we maintain the format strings, even when debugging is disabled */
+static inline void _dbprintk(const char *fmt, ...)
+ __attribute__((format(printf,1,2)));
+static inline void _dbprintk(const char *fmt, ...)
+{
+}
+
+#define kenter(FMT,...) dbgprintk("==> %s("FMT")",__FUNCTION__ ,##__VA_ARGS__)
+#define kleave(FMT,...) dbgprintk("<== %s()"FMT"",__FUNCTION__ ,##__VA_ARGS__)
+#define kdebug(FMT,...) dbgprintk(FMT ,##__VA_ARGS__)
+
+
+#if defined(__KDEBUG)
+#define _enter(FMT,...) kenter(FMT,##__VA_ARGS__)
+#define _leave(FMT,...) kleave(FMT,##__VA_ARGS__)
+#define _debug(FMT,...) kdebug(FMT,##__VA_ARGS__)
+
+#elif defined(CONFIG_CACHEFILES_DEBUG)
+#define _enter(FMT,...) \
+do { \
+ if (cachefiles_debug & CACHEFILES_DEBUG_KENTER) \
+ kenter(FMT,##__VA_ARGS__); \
+} while (0)
+
+#define _leave(FMT,...) \
+do { \
+ if (cachefiles_debug & CACHEFILES_DEBUG_KLEAVE) \
+ kleave(FMT,##__VA_ARGS__); \
+} while (0)
+
+#define _debug(FMT,...) \
+do { \
+ if (cachefiles_debug & CACHEFILES_DEBUG_KDEBUG) \
+ kdebug(FMT,##__VA_ARGS__); \
+} while (0)
+
+#else
+#define _enter(FMT,...) _dbprintk("==> %s("FMT")",__FUNCTION__ ,##__VA_ARGS__)
+#define _leave(FMT,...) _dbprintk("<== %s()"FMT"",__FUNCTION__ ,##__VA_ARGS__)
+#define _debug(FMT,...) _dbprintk(FMT ,##__VA_ARGS__)
+#endif
+
+#if 1 // defined(__KDEBUGALL)
+
+#define ASSERT(X) \
+do { \
+ if (unlikely(!(X))) { \
+ printk(KERN_ERR "\n"); \
+ printk(KERN_ERR "CacheFiles: Assertion failed\n"); \
+ BUG(); \
+ } \
+} while(0)
+
+#define ASSERTCMP(X, OP, Y) \
+do { \
+ if (unlikely(!((X) OP (Y)))) { \
+ printk(KERN_ERR "\n"); \
+ printk(KERN_ERR "CacheFiles: Assertion failed\n"); \
+ printk(KERN_ERR "%lx " #OP " %lx is false\n", \
+ (unsigned long)(X), (unsigned long)(Y)); \
+ BUG(); \
+ } \
+} while(0)
+
+#define ASSERTIF(C, X) \
+do { \
+ if (unlikely((C) && !(X))) { \
+ printk(KERN_ERR "\n"); \
+ printk(KERN_ERR "CacheFiles: Assertion failed\n"); \
+ BUG(); \
+ } \
+} while(0)
+
+#define ASSERTIFCMP(C, X, OP, Y) \
+do { \
+ if (unlikely((C) && !((X) OP (Y)))) { \
+ printk(KERN_ERR "\n"); \
+ printk(KERN_ERR "CacheFiles: Assertion failed\n"); \
+ printk(KERN_ERR "%lx " #OP " %lx is false\n", \
+ (unsigned long)(X), (unsigned long)(Y)); \
+ BUG(); \
+ } \
+} while(0)
+
+#else
+
+#define ASSERT(X) \
+do { \
+} while(0)
+
+#define ASSERTCMP(X, OP, Y) \
+do { \
+} while(0)
+
+#define ASSERTIF(C, X) \
+do { \
+} while(0)
+
+#define ASSERTIFCMP(C, X, OP, Y) \
+do { \
+} while(0)
+
+#endif
diff --git a/fs/cachefiles/cf-key.c b/fs/cachefiles/cf-key.c
new file mode 100644
index 0000000..6956eec
--- /dev/null
+++ b/fs/cachefiles/cf-key.c
@@ -0,0 +1,159 @@
+/* Key to pathname encoder
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include "cf-internal.h"
+
+static const char cachefiles_charmap[64] =
+ "0123456789" /* 0 - 9 */
+ "abcdefghijklmnopqrstuvwxyz" /* 10 - 35 */
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" /* 36 - 61 */
+ "_-" /* 62 - 63 */
+ ;
+
+static const char cachefiles_filecharmap[256] = {
+ /* we skip space and tab and control chars */
+ [ 33 ... 46 ] = 1, /* '!' -> '.' */
+ /* we skip '/' as it's significant to pathwalk */
+ [ 48 ... 127 ] = 1, /* '0' -> '~' */
+};
+
+/*
+ * turn the raw key into something cooked
+ * - the raw key should include the length in the two bytes at the front
+ * - the key may be up to 514 bytes in length (including the length word)
+ * - "base64" encode the strange keys, mapping 3 bytes of raw to four of
+ * cooked
+ * - need to cut the cooked key into 252 char lengths (189 raw bytes)
+ */
+char *cachefiles_cook_key(const u8 *raw, int keylen, uint8_t type)
+{
+ unsigned char csum, ch;
+ unsigned int acc;
+ char *key;
+ int loop, len, max, seg, mark, print;
+
+ _enter(",%d", keylen);
+
+ BUG_ON(keylen < 2 || keylen > 514);
+
+ csum = raw[0] + raw[1];
+ print = 1;
+ for (loop = 2; loop < keylen; loop++) {
+ ch = raw[loop];
+ csum += ch;
+ print &= cachefiles_filecharmap[ch];
+ }
+
+ if (print) {
+ /* if the path is usable ASCII, then we render it directly */
+ max = keylen - 2;
+ max += 2; /* two base64'd length chars on the front */
+ max += 5; /* @checksum/M */
+ max += 3 * 2; /* maximum number of segment dividers (".../M")
+ * is ((514 + 251) / 252) = 3
+ */
+ max += 1; /* NUL on end */
+ } else {
+ /* calculate the maximum length of the cooked key */
+ keylen = (keylen + 2) / 3;
+
+ max = keylen * 4;
+ max += 5; /* @checksum/M */
+ max += 3 * 2; /* maximum number of segment dividers (".../M")
+ * is ((514 + 188) / 189) = 3
+ */
+ max += 1; /* NUL on end */
+ }
+
+ max += 1; /* 2nd NUL on end */
+
+ _debug("max: %d", max);
+
+ key = kmalloc(max, GFP_KERNEL);
+ if (!key)
+ return NULL;
+
+ len = 0;
+
+ /* build the cooked key */
+ sprintf(key, "@%02x%c+", (unsigned) csum, 0);
+ len = 5;
+ mark = len - 1;
+
+ if (print) {
+ acc = *(uint16_t *) raw;
+ raw += 2;
+
+ key[len + 1] = cachefiles_charmap[acc & 63];
+ acc >>= 6;
+ key[len] = cachefiles_charmap[acc & 63];
+ len += 2;
+
+ seg = 250;
+ for (loop = keylen; loop > 0; loop--) {
+ if (seg <= 0) {
+ key[len++] = '\0';
+ mark = len;
+ key[len++] = '+';
+ seg = 252;
+ }
+
+ key[len++] = *raw++;
+ ASSERT(len < max);
+ }
+
+ switch (type) {
+ case FSCACHE_COOKIE_TYPE_INDEX: type = 'I'; break;
+ case FSCACHE_COOKIE_TYPE_DATAFILE: type = 'D'; break;
+ default: type = 'S'; break;
+ }
+ } else {
+ seg = 252;
+ for (loop = keylen; loop > 0; loop--) {
+ if (seg <= 0) {
+ key[len++] = '\0';
+ mark = len;
+ key[len++] = '+';
+ seg = 252;
+ }
+
+ acc = *raw++;
+ acc |= *raw++ << 8;
+ acc |= *raw++ << 16;
+
+ _debug("acc: %06x", acc);
+
+ key[len++] = cachefiles_charmap[acc & 63];
+ acc >>= 6;
+ key[len++] = cachefiles_charmap[acc & 63];
+ acc >>= 6;
+ key[len++] = cachefiles_charmap[acc & 63];
+ acc >>= 6;
+ key[len++] = cachefiles_charmap[acc & 63];
+
+ ASSERT(len < max);
+ }
+
+ switch (type) {
+ case FSCACHE_COOKIE_TYPE_INDEX: type = 'J'; break;
+ case FSCACHE_COOKIE_TYPE_DATAFILE: type = 'E'; break;
+ default: type = 'T'; break;
+ }
+ }
+
+ key[mark] = type;
+ key[len++] = 0;
+ key[len] = 0;
+
+ _leave(" = %p %d", key, len);
+ return key;
+}
diff --git a/fs/cachefiles/cf-main.c b/fs/cachefiles/cf-main.c
new file mode 100644
index 0000000..67bfdb3
--- /dev/null
+++ b/fs/cachefiles/cf-main.c
@@ -0,0 +1,109 @@
+/* Network filesystem caching backend to use cache files on a premounted
+ * filesystem
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/statfs.h>
+#include <linux/sysctl.h>
+#include <linux/miscdevice.h>
+#include "cf-internal.h"
+
+unsigned cachefiles_debug;
+module_param_named(debug, cachefiles_debug, uint, S_IWUSR | S_IRUGO);
+MODULE_PARM_DESC(cachefiles_debug, "CacheFiles debugging mask");
+
+MODULE_DESCRIPTION("Mounted-filesystem based cache");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+struct kmem_cache *cachefiles_object_jar;
+
+static struct miscdevice cachefiles_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "cachefiles",
+ .fops = &cachefiles_daemon_fops,
+};
+
+static void cachefiles_object_init_once(void *_object,
+ struct kmem_cache *cachep,
+ unsigned long flags)
+{
+ struct cachefiles_object *object = _object;
+
+ memset(object, 0, sizeof(*object));
+ fscache_object_init(&object->fscache);
+ spin_lock_init(&object->work_lock);
+}
+
+/*
+ * initialise the fs caching module
+ */
+static int __init cachefiles_init(void)
+{
+ int ret;
+
+ ret = misc_register(&cachefiles_dev);
+ if (ret < 0)
+ goto error_dev;
+
+ /* create an object jar */
+ ret = -ENOMEM;
+ cachefiles_object_jar =
+ kmem_cache_create("cachefiles_object_jar",
+ sizeof(struct cachefiles_object),
+ 0,
+ SLAB_HWCACHE_ALIGN,
+ cachefiles_object_init_once);
+ if (!cachefiles_object_jar) {
+ printk(KERN_NOTICE
+ "CacheFiles: Failed to allocate an object jar\n");
+ goto error_object_jar;
+ }
+
+ ret = cachefiles_proc_init();
+ if (ret < 0)
+ goto error_proc;
+
+ printk(KERN_INFO "CacheFiles: Loaded\n");
+ return 0;
+
+error_proc:
+ kmem_cache_destroy(cachefiles_object_jar);
+error_object_jar:
+ misc_deregister(&cachefiles_dev);
+error_dev:
+ kerror("failed to register: %d", ret);
+ return ret;
+}
+
+fs_initcall(cachefiles_init);
+
+/*
+ * clean up on module removal
+ */
+static void __exit cachefiles_exit(void)
+{
+ printk(KERN_INFO "CacheFiles: Unloading\n");
+
+ cachefiles_proc_cleanup();
+ kmem_cache_destroy(cachefiles_object_jar);
+ misc_deregister(&cachefiles_dev);
+}
+
+module_exit(cachefiles_exit);
diff --git a/fs/cachefiles/cf-namei.c b/fs/cachefiles/cf-namei.c
new file mode 100644
index 0000000..809ddcb
--- /dev/null
+++ b/fs/cachefiles/cf-namei.c
@@ -0,0 +1,743 @@
+/* CacheFiles path walking and related routines
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/fsnotify.h>
+#include <linux/quotaops.h>
+#include <linux/xattr.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/security.h>
+#include "cf-internal.h"
+
+static int cachefiles_wait_bit(void *flags)
+{
+ schedule();
+ return 0;
+}
+
+/*
+ * record the fact that an object is now active
+ */
+static void cachefiles_mark_object_active(struct cachefiles_cache *cache,
+ struct cachefiles_object *object)
+{
+ struct cachefiles_object *xobject;
+ struct rb_node **_p, *_parent = NULL;
+ struct dentry *dentry;
+
+ _enter(",%p", object);
+
+try_again:
+ write_lock(&cache->active_lock);
+
+ if (test_and_set_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags))
+ BUG();
+
+ dentry = object->dentry;
+ _p = &cache->active_nodes.rb_node;
+ while (*_p) {
+ _parent = *_p;
+ xobject = rb_entry(_parent,
+ struct cachefiles_object, active_node);
+
+ if (xobject->dentry > dentry)
+ _p = &(*_p)->rb_left;
+ else if (xobject->dentry < dentry)
+ _p = &(*_p)->rb_right;
+ else
+ goto wait_for_old_object;
+ }
+
+ rb_link_node(&object->active_node, _parent, _p);
+ rb_insert_color(&object->active_node, &cache->active_nodes);
+
+ write_unlock(&cache->active_lock);
+ _leave("");
+ return;
+
+ /* an old object from a previous incarnation is hogging the slot - we
+ * need to wait for it to be destroyed */
+wait_for_old_object:
+ _debug("old OBJ%x", xobject->fscache.debug_id);
+ ASSERTCMP(xobject->fscache.state, >=, FSCACHE_OBJECT_DYING);
+ atomic_inc(&xobject->usage);
+ //clear_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags);
+ write_unlock(&cache->active_lock);
+
+ _debug(">>> wait");
+ wait_on_bit(&xobject->flags, CACHEFILES_OBJECT_ACTIVE,
+ cachefiles_wait_bit, TASK_UNINTERRUPTIBLE);
+ _debug("<<< waited");
+
+ cache->cache.ops->put_object(&xobject->fscache);
+ goto try_again;
+}
+
+/*
+ * delete an object representation from the cache
+ * - file backed objects are unlinked
+ * - directory backed objects are stuffed into the graveyard for userspace to
+ * delete
+ * - unlocks the directory mutex
+ */
+static int cachefiles_bury_object(struct cachefiles_cache *cache,
+ struct dentry *dir,
+ struct dentry *rep)
+{
+ struct dentry *grave, *trap;
+ char nbuffer[8 + 8 + 1];
+ int ret;
+
+ _enter(",'%*.*s','%*.*s'",
+ dir->d_name.len, dir->d_name.len, dir->d_name.name,
+ rep->d_name.len, rep->d_name.len, rep->d_name.name);
+
+ /* non-directories can just be unlinked */
+ if (!S_ISDIR(rep->d_inode->i_mode)) {
+ _debug("unlink stale object");
+ ret = vfs_unlink(dir->d_inode, rep);
+
+ mutex_unlock(&dir->d_inode->i_mutex);
+
+ if (ret == -EIO)
+ cachefiles_io_error(cache, "Unlink failed");
+
+ _leave(" = %d", ret);
+ return ret;
+ }
+
+ /* directories have to be moved to the graveyard */
+ _debug("move stale object to graveyard");
+ mutex_unlock(&dir->d_inode->i_mutex);
+
+try_again:
+ /* first step is to make up a grave dentry in the graveyard */
+ sprintf(nbuffer, "%08x%08x",
+ (uint32_t) xtime.tv_sec,
+ (uint32_t) atomic_inc_return(&cache->gravecounter));
+
+ /* do the multiway lock magic */
+ trap = lock_rename(cache->graveyard, dir);
+
+ /* do some checks before getting the grave dentry */
+ if (rep->d_parent != dir) {
+ /* the entry was probably culled when we dropped the parent dir
+ * lock */
+ unlock_rename(cache->graveyard, dir);
+ _leave(" = 0 [culled?]");
+ return 0;
+ }
+
+ if (!S_ISDIR(cache->graveyard->d_inode->i_mode)) {
+ unlock_rename(cache->graveyard, dir);
+ cachefiles_io_error(cache, "Graveyard no longer a directory");
+ return -EIO;
+ }
+
+ if (trap == rep) {
+ unlock_rename(cache->graveyard, dir);
+ cachefiles_io_error(cache, "May not make directory loop");
+ return -EIO;
+ }
+
+ if (d_mountpoint(rep)) {
+ unlock_rename(cache->graveyard, dir);
+ cachefiles_io_error(cache, "Mountpoint in cache");
+ return -EIO;
+ }
+
+ grave = lookup_one_len(nbuffer, cache->graveyard, strlen(nbuffer));
+ if (IS_ERR(grave)) {
+ unlock_rename(cache->graveyard, dir);
+
+ if (PTR_ERR(grave) == -ENOMEM) {
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+ }
+
+ cachefiles_io_error(cache, "Lookup error %ld",
+ PTR_ERR(grave));
+ return -EIO;
+ }
+
+ if (grave->d_inode) {
+ unlock_rename(cache->graveyard, dir);
+ dput(grave);
+ grave = NULL;
+ cond_resched();
+ goto try_again;
+ }
+
+ if (d_mountpoint(grave)) {
+ unlock_rename(cache->graveyard, dir);
+ dput(grave);
+ cachefiles_io_error(cache, "Mountpoint in graveyard");
+ return -EIO;
+ }
+
+ /* target should not be an ancestor of source */
+ if (trap == grave) {
+ unlock_rename(cache->graveyard, dir);
+ dput(grave);
+ cachefiles_io_error(cache, "May not make directory loop");
+ return -EIO;
+ }
+
+ /* attempt the rename */
+ ret = vfs_rename(dir->d_inode, rep, cache->graveyard->d_inode, grave);
+ if (ret != 0 && ret != -ENOMEM)
+ cachefiles_io_error(cache, "Rename failed with error %d", ret);
+
+ unlock_rename(cache->graveyard, dir);
+ dput(grave);
+ _leave(" = 0");
+ return 0;
+}
+
+/*
+ * delete an object representation from the cache
+ */
+int cachefiles_delete_object(struct cachefiles_cache *cache,
+ struct cachefiles_object *object)
+{
+ struct dentry *dir;
+ int ret;
+
+ _enter(",{%p}", object->dentry);
+
+ ASSERT(object->dentry);
+ ASSERT(object->dentry->d_inode);
+ ASSERT(object->dentry->d_parent);
+
+ dir = dget_parent(object->dentry);
+
+ mutex_lock(&dir->d_inode->i_mutex);
+ ret = cachefiles_bury_object(cache, dir, object->dentry);
+
+ dput(dir);
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * walk from the parent object to the child object through the backing
+ * filesystem, creating directories as we go
+ */
+int cachefiles_walk_to_object(struct cachefiles_object *parent,
+ struct cachefiles_object *object,
+ const char *key,
+ struct cachefiles_xattr *auxdata)
+{
+ struct cachefiles_cache *cache;
+ struct dentry *dir, *next = NULL;
+ unsigned long start;
+ const char *name;
+ int ret, nlen;
+
+ _enter("{%p},,%s,", parent->dentry, key);
+
+ cache = container_of(parent->fscache.cache,
+ struct cachefiles_cache, cache);
+
+ ASSERT(parent->dentry);
+ ASSERT(parent->dentry->d_inode);
+
+ if (!(S_ISDIR(parent->dentry->d_inode->i_mode))) {
+ // TODO: convert file to dir
+ _leave("looking up in none directory");
+ return -ENOBUFS;
+ }
+
+ dir = dget(parent->dentry);
+
+advance:
+ /* attempt to transit the first directory component */
+ name = key;
+ nlen = strlen(key);
+
+ /* key ends in a double NUL */
+ key = key + nlen + 1;
+ if (!*key)
+ key = NULL;
+
+lookup_again:
+ /* search the current directory for the element name */
+ _debug("lookup '%s'", name);
+
+ mutex_lock(&dir->d_inode->i_mutex);
+
+ start = jiffies;
+ next = lookup_one_len(name, dir, nlen);
+ cachefiles_hist(cachefiles_lookup_histogram, start);
+ if (IS_ERR(next))
+ goto lookup_error;
+
+ _debug("next -> %p %s", next, next->d_inode ? "positive" : "negative");
+
+ if (!key)
+ object->new = !next->d_inode;
+
+ /* if this element of the path doesn't exist, then the lookup phase
+ * failed, and we can release any readers in the certain knowledge that
+ * there's nothing for them to actually read */
+ if (!next->d_inode)
+ fscache_object_lookup_negative(&object->fscache);
+
+ /* we need to create the object if it's negative */
+ if (key || object->type == FSCACHE_COOKIE_TYPE_INDEX) {
+ /* index objects and intervening tree levels must be subdirs */
+ if (!next->d_inode) {
+ // TODO advance object state
+ ret = cachefiles_has_space(cache, 1, 0);
+ if (ret < 0)
+ goto create_error;
+
+ start = jiffies;
+ ret = vfs_mkdir(dir->d_inode, next, 0);
+ cachefiles_hist(cachefiles_mkdir_histogram, start);
+ if (ret < 0)
+ goto create_error;
+
+ ASSERT(next->d_inode);
+
+ _debug("mkdir -> %p{%p{ino=%lu}}",
+ next, next->d_inode, next->d_inode->i_ino);
+
+ } else if (!S_ISDIR(next->d_inode->i_mode)) {
+ kerror("inode %lu is not a directory",
+ next->d_inode->i_ino);
+ ret = -ENOBUFS;
+ goto error;
+ }
+
+ } else {
+ /* non-index objects start out life as files */
+ if (!next->d_inode) {
+ // TODO advance object state
+ ret = cachefiles_has_space(cache, 1, 0);
+ if (ret < 0)
+ goto create_error;
+
+ start = jiffies;
+ ret = vfs_create(dir->d_inode, next, S_IFREG, NULL);
+ cachefiles_hist(cachefiles_create_histogram, start);
+ if (ret < 0)
+ goto create_error;
+
+ ASSERT(next->d_inode);
+
+ _debug("create -> %p{%p{ino=%lu}}",
+ next, next->d_inode, next->d_inode->i_ino);
+
+ } else if (!S_ISDIR(next->d_inode->i_mode) &&
+ !S_ISREG(next->d_inode->i_mode)
+ ) {
+ kerror("inode %lu is not a file or directory",
+ next->d_inode->i_ino);
+ ret = -ENOBUFS;
+ goto error;
+ }
+ }
+
+ /* process the next component */
+ if (key) {
+ _debug("advance");
+ mutex_unlock(&dir->d_inode->i_mutex);
+ dput(dir);
+ dir = next;
+ next = NULL;
+ goto advance;
+ }
+
+ /* we've found the object we were looking for */
+ object->dentry = next;
+
+ /* if we've found that the terminal object exists, then we need to
+ * check its attributes and delete it if it's out of date */
+ if (!object->new) {
+ _debug("validate '%*.*s'",
+ next->d_name.len, next->d_name.len, next->d_name.name);
+
+ ret = cachefiles_check_object_xattr(object, auxdata);
+ if (ret == -ESTALE) {
+ /* delete the object (the deleter drops the directory
+ * mutex) */
+ object->dentry = NULL;
+
+ ret = cachefiles_bury_object(cache, dir, next);
+ dput(next);
+ next = NULL;
+
+ if (ret < 0)
+ goto delete_error;
+
+ _debug("redo lookup");
+ goto lookup_again;
+ }
+ }
+
+ /* note that we're now using this object */
+ cachefiles_mark_object_active(cache, object);
+
+ mutex_unlock(&dir->d_inode->i_mutex);
+ dput(dir);
+ dir = NULL;
+
+ _debug("=== OBTAINED_OBJECT ===");
+
+ if (object->new) {
+ /* attach data to a newly constructed terminal object */
+ ret = cachefiles_set_object_xattr(object, auxdata);
+ if (ret < 0)
+ goto check_error;
+ } else {
+ /* always update the atime on an object we've just looked up
+ * (this is used to keep track of culling, and atimes are only
+ * updated by read, write and readdir but not lookup or
+ * open) */
+ touch_atime(cache->mnt, next);
+ }
+
+ /* open a file interface onto a data file */
+ if (object->type != FSCACHE_COOKIE_TYPE_INDEX) {
+ if (S_ISREG(object->dentry->d_inode->i_mode)) {
+ const struct address_space_operations *aops;
+
+ ret = -EPERM;
+ aops = object->dentry->d_inode->i_mapping->a_ops;
+ if (!aops->bmap ||
+ !aops->prepare_write ||
+ !aops->commit_write ||
+ !aops->write_one_page)
+ goto check_error;
+
+ object->backer = object->dentry;
+ } else {
+ BUG(); // TODO: open file in data-class subdir
+ }
+ }
+
+ object->new = 0;
+ fscache_obtained_object(&object->fscache);
+
+ _leave(" = 0 [%lu]", object->dentry->d_inode->i_ino);
+ return 0;
+
+create_error:
+ _debug("create error %d", ret);
+ if (ret == -EIO)
+ cachefiles_io_error(cache, "Create/mkdir failed");
+ goto error;
+
+check_error:
+ _debug("check error %d", ret);
+ write_lock(&cache->active_lock);
+ rb_erase(&object->active_node, &cache->active_nodes);
+ write_unlock(&cache->active_lock);
+
+ dput(object->dentry);
+ object->dentry = NULL;
+ goto error_out;
+
+delete_error:
+ _debug("delete error %d", ret);
+ goto error_out2;
+
+lookup_error:
+ _debug("lookup error %ld", PTR_ERR(next));
+ ret = PTR_ERR(next);
+ if (ret == -EIO)
+ cachefiles_io_error(cache, "Lookup failed");
+ next = NULL;
+error:
+ mutex_unlock(&dir->d_inode->i_mutex);
+ dput(next);
+error_out2:
+ dput(dir);
+error_out:
+ if (ret == -ENOSPC)
+ ret = -ENOBUFS;
+
+ _leave(" = error %d", -ret);
+ return ret;
+}
+
+/*
+ * get a subdirectory
+ */
+struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
+ struct dentry *dir,
+ const char *dirname)
+{
+ struct dentry *subdir;
+ unsigned long start;
+ int ret;
+
+ _enter(",,%s", dirname);
+
+ /* search the current directory for the element name */
+ mutex_lock(&dir->d_inode->i_mutex);
+
+ start = jiffies;
+ subdir = lookup_one_len(dirname, dir, strlen(dirname));
+ cachefiles_hist(cachefiles_lookup_histogram, start);
+ if (IS_ERR(subdir)) {
+ if (PTR_ERR(subdir) == -ENOMEM)
+ goto nomem_d_alloc;
+ goto lookup_error;
+ }
+
+ _debug("subdir -> %p %s",
+ subdir, subdir->d_inode ? "positive" : "negative");
+
+ /* we need to create the subdir if it doesn't exist yet */
+ if (!subdir->d_inode) {
+ ret = cachefiles_has_space(cache, 1, 0);
+ if (ret < 0)
+ goto mkdir_error;
+
+ _debug("attempt mkdir");
+
+ ret = vfs_mkdir(dir->d_inode, subdir, 0700);
+ if (ret < 0)
+ goto mkdir_error;
+
+ ASSERT(subdir->d_inode);
+
+ _debug("mkdir -> %p{%p{ino=%lu}}",
+ subdir,
+ subdir->d_inode,
+ subdir->d_inode->i_ino);
+ }
+
+ mutex_unlock(&dir->d_inode->i_mutex);
+
+ /* we need to make sure the subdir is a directory */
+ ASSERT(subdir->d_inode);
+
+ if (!S_ISDIR(subdir->d_inode->i_mode)) {
+ kerror("%s is not a directory", dirname);
+ ret = -EIO;
+ goto check_error;
+ }
+
+ ret = -EPERM;
+ if (!subdir->d_inode->i_op ||
+ !subdir->d_inode->i_op->setxattr ||
+ !subdir->d_inode->i_op->getxattr ||
+ !subdir->d_inode->i_op->lookup ||
+ !subdir->d_inode->i_op->mkdir ||
+ !subdir->d_inode->i_op->create ||
+ !subdir->d_inode->i_op->rename ||
+ !subdir->d_inode->i_op->rmdir ||
+ !subdir->d_inode->i_op->unlink)
+ goto check_error;
+
+ _leave(" = [%lu]", subdir->d_inode->i_ino);
+ return subdir;
+
+check_error:
+ dput(subdir);
+ _leave(" = %d [check]", ret);
+ return ERR_PTR(ret);
+
+mkdir_error:
+ mutex_unlock(&dir->d_inode->i_mutex);
+ dput(subdir);
+ kerror("mkdir %s failed with error %d", dirname, ret);
+ return ERR_PTR(ret);
+
+lookup_error:
+ mutex_unlock(&dir->d_inode->i_mutex);
+ ret = PTR_ERR(subdir);
+ kerror("Lookup %s failed with error %d", dirname, ret);
+ return ERR_PTR(ret);
+
+nomem_d_alloc:
+ mutex_unlock(&dir->d_inode->i_mutex);
+ _leave(" = -ENOMEM");
+ return ERR_PTR(-ENOMEM);
+}
+
+/*
+ * find out if an object is in use or not
+ * - if finds object and it's not in use:
+ * - returns a pointer to the object and a reference on it
+ * - returns with the directory locked
+ */
+static struct dentry *cachefiles_check_active(struct cachefiles_cache *cache,
+ struct dentry *dir,
+ char *filename)
+{
+ struct cachefiles_object *object;
+ struct rb_node *_n;
+ struct dentry *victim;
+ unsigned long start;
+ int ret;
+
+ _enter(",%*.*s/,%s",
+ dir->d_name.len, dir->d_name.len, dir->d_name.name, filename);
+
+ /* look up the victim */
+ mutex_lock_nested(&dir->d_inode->i_mutex, 1);
+
+ start = jiffies;
+ victim = lookup_one_len(filename, dir, strlen(filename));
+ cachefiles_hist(cachefiles_lookup_histogram, start);
+ if (IS_ERR(victim))
+ goto lookup_error;
+
+ _debug("victim -> %p %s",
+ victim, victim->d_inode ? "positive" : "negative");
+
+ /* if the object is no longer there then we probably retired the object
+ * at the netfs's request whilst the cull was in progress
+ */
+ if (!victim->d_inode) {
+ mutex_unlock(&dir->d_inode->i_mutex);
+ dput(victim);
+ _leave(" = -ENOENT [absent]");
+ return ERR_PTR(-ENOENT);
+ }
+
+ /* check to see if we're using this object */
+ read_lock(&cache->active_lock);
+
+ _n = cache->active_nodes.rb_node;
+
+ while (_n) {
+ object = rb_entry(_n, struct cachefiles_object, active_node);
+
+ if (object->dentry > victim)
+ _n = _n->rb_left;
+ else if (object->dentry < victim)
+ _n = _n->rb_right;
+ else
+ goto object_in_use;
+ }
+
+ read_unlock(&cache->active_lock);
+
+ _leave(" = %p", victim);
+ return victim;
+
+object_in_use:
+ read_unlock(&cache->active_lock);
+ mutex_unlock(&dir->d_inode->i_mutex);
+ dput(victim);
+ _leave(" = -EBUSY [in use]");
+ return ERR_PTR(-EBUSY);
+
+lookup_error:
+ mutex_unlock(&dir->d_inode->i_mutex);
+ ret = PTR_ERR(victim);
+ if (ret == -ENOENT) {
+ /* file or dir now absent - probably retired by netfs */
+ _leave(" = -ESTALE [absent]");
+ return ERR_PTR(-ESTALE);
+ }
+
+ if (ret == -EIO) {
+ cachefiles_io_error(cache, "Lookup failed");
+ } else if (ret != -ENOMEM) {
+ kerror("Internal error: %d", ret);
+ ret = -EIO;
+ }
+
+ _leave(" = %d", ret);
+ return ERR_PTR(ret);
+}
+
+/*
+ * cull an object if it's not in use
+ * - called only by cache manager daemon
+ */
+int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
+ char *filename)
+{
+ struct dentry *victim;
+ int ret;
+
+ _enter(",%*.*s/,%s",
+ dir->d_name.len, dir->d_name.len, dir->d_name.name, filename);
+
+ victim = cachefiles_check_active(cache, dir, filename);
+ if (IS_ERR(victim))
+ return PTR_ERR(victim);
+
+ _debug("victim -> %p %s",
+ victim, victim->d_inode ? "positive" : "negative");
+
+ /* okay... the victim is not being used so we can cull it
+ * - start by marking it as stale
+ */
+ _debug("victim is cullable");
+
+ ret = cachefiles_remove_object_xattr(cache, victim);
+ if (ret < 0)
+ goto error_unlock;
+
+ /* actually remove the victim (drops the dir mutex) */
+ _debug("bury");
+
+ ret = cachefiles_bury_object(cache, dir, victim);
+ if (ret < 0)
+ goto error;
+
+ dput(victim);
+ _leave(" = 0");
+ return 0;
+
+error_unlock:
+ mutex_unlock(&dir->d_inode->i_mutex);
+error:
+ dput(victim);
+ if (ret == -ENOENT) {
+ /* file or dir now absent - probably retired by netfs */
+ _leave(" = -ESTALE [absent]");
+ return -ESTALE;
+ }
+
+ if (ret != -ENOMEM) {
+ kerror("Internal error: %d", ret);
+ ret = -EIO;
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * find out if an object is in use or not
+ * - called only by cache manager daemon
+ * - returns -EBUSY or 0 to indicate whether an object is in use or not
+ */
+int cachefiles_check_in_use(struct cachefiles_cache *cache, struct dentry *dir,
+ char *filename)
+{
+ struct dentry *victim;
+
+ _enter(",%*.*s/,%s",
+ dir->d_name.len, dir->d_name.len, dir->d_name.name, filename);
+
+ victim = cachefiles_check_active(cache, dir, filename);
+ if (IS_ERR(victim))
+ return PTR_ERR(victim);
+
+ mutex_unlock(&dir->d_inode->i_mutex);
+ dput(victim);
+ _leave(" = 0");
+ return 0;
+}
diff --git a/fs/cachefiles/cf-proc.c b/fs/cachefiles/cf-proc.c
new file mode 100644
index 0000000..c0d5444
--- /dev/null
+++ b/fs/cachefiles/cf-proc.c
@@ -0,0 +1,166 @@
+/* CacheFiles statistics
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include "cf-internal.h"
+
+struct cachefiles_proc {
+ unsigned nlines;
+ const struct seq_operations *ops;
+};
+
+atomic_t cachefiles_lookup_histogram[HZ];
+atomic_t cachefiles_mkdir_histogram[HZ];
+atomic_t cachefiles_create_histogram[HZ];
+
+static struct proc_dir_entry *proc_cachefiles;
+
+static int cachefiles_proc_open(struct inode *inode, struct file *file);
+static void *cachefiles_proc_start(struct seq_file *m, loff_t *pos);
+static void cachefiles_proc_stop(struct seq_file *m, void *v);
+static void *cachefiles_proc_next(struct seq_file *m, void *v, loff_t *pos);
+static int cachefiles_histogram_show(struct seq_file *m, void *v);
+
+static const struct file_operations cachefiles_proc_fops = {
+ .open = cachefiles_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+static const struct seq_operations cachefiles_histogram_ops = {
+ .start = cachefiles_proc_start,
+ .stop = cachefiles_proc_stop,
+ .next = cachefiles_proc_next,
+ .show = cachefiles_histogram_show,
+};
+
+static const struct cachefiles_proc cachefiles_histogram = {
+ .nlines = HZ + 1,
+ .ops = &cachefiles_histogram_ops,
+};
+
+/*
+ * initialise the /proc/fs/fscache/cachefiles/ directory
+ */
+int __init cachefiles_proc_init(void)
+{
+ struct proc_dir_entry *p;
+
+ _enter("");
+
+ proc_cachefiles = proc_mkdir("cachefiles", proc_fscache);
+ if (!proc_cachefiles)
+ goto error_dir;
+ proc_cachefiles->owner = THIS_MODULE;
+
+ p = create_proc_entry("histogram", 0, proc_cachefiles);
+ if (!p)
+ goto error_histogram;
+ p->proc_fops = &cachefiles_proc_fops;
+ p->owner = THIS_MODULE;
+ p->data = (void *) &cachefiles_histogram;
+
+ _leave(" = 0");
+ return 0;
+
+error_histogram:
+ remove_proc_entry("fs/cachefiles", NULL);
+error_dir:
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+}
+
+/*
+ * clean up the /proc/fs/fscache/cachefiles/ directory
+ */
+void cachefiles_proc_cleanup(void)
+{
+ remove_proc_entry("histogram", proc_cachefiles);
+ remove_proc_entry("cachefiles", proc_fscache);
+}
+
+/*
+ * open "/proc/fs/fscache/cachefiles/XXX" which provide statistics summaries
+ */
+static int cachefiles_proc_open(struct inode *inode, struct file *file)
+{
+ const struct cachefiles_proc *proc = PDE(inode)->data;
+ struct seq_file *m;
+ int ret;
+
+ ret = seq_open(file, proc->ops);
+ if (ret == 0) {
+ m = file->private_data;
+ m->private = (void *) proc;
+ }
+ return ret;
+}
+
+/*
+ * set up the iterator to start reading from the first line
+ */
+static void *cachefiles_proc_start(struct seq_file *m, loff_t *_pos)
+{
+ if (*_pos == 0)
+ *_pos = 1;
+ return (void *)(unsigned long) *_pos;
+}
+
+/*
+ * move to the next line
+ */
+static void *cachefiles_proc_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ const struct cachefiles_proc *proc = m->private;
+
+ (*pos)++;
+ return *pos > proc->nlines ? NULL : (void *)(unsigned long) *pos;
+}
+
+/*
+ * clean up after reading
+ */
+static void cachefiles_proc_stop(struct seq_file *m, void *v)
+{
+}
+
+/*
+ * display the time-taken histogram
+ */
+static int cachefiles_histogram_show(struct seq_file *m, void *v)
+{
+ unsigned long index;
+ unsigned x, y, z, t;
+
+ switch ((unsigned long) v) {
+ case 1:
+ seq_puts(m, "JIFS SECS LOOKUPS MKDIRS CREATES\n");
+ return 0;
+ case 2:
+ seq_puts(m, "===== ===== ========= ========= =========\n");
+ return 0;
+ default:
+ index = (unsigned long) v - 3;
+ x = atomic_read(&cachefiles_lookup_histogram[index]);
+ y = atomic_read(&cachefiles_mkdir_histogram[index]);
+ z = atomic_read(&cachefiles_create_histogram[index]);
+ if (x == 0 && y == 0 && z == 0)
+ return 0;
+
+ t = (index * 1000) / HZ;
+
+ seq_printf(m, "%4lu 0.%03u %9u %9u %9u\n", index, t, x, y, z);
+ return 0;
+ }
+}
diff --git a/fs/cachefiles/cf-rdwr.c b/fs/cachefiles/cf-rdwr.c
new file mode 100644
index 0000000..5233477
--- /dev/null
+++ b/fs/cachefiles/cf-rdwr.c
@@ -0,0 +1,849 @@
+/* Storage object read/write
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include "cf-internal.h"
+
+/*
+ * detect wake up events generated by the unlocking of pages in which we're
+ * interested
+ * - we use this to detect read completion of backing pages
+ * - the caller holds the waitqueue lock
+ */
+static int cachefiles_read_waiter(wait_queue_t *wait, unsigned mode,
+ int sync, void *_key)
+{
+ struct cachefiles_one_read *monitor =
+ container_of(wait, struct cachefiles_one_read, monitor);
+ struct cachefiles_object *object;
+ struct wait_bit_key *key = _key;
+ struct page *page = wait->private;
+
+ ASSERT(key);
+
+ _enter("{%lu},%u,%d,{%p,%u}",
+ monitor->netfs_page->index, mode, sync,
+ key->flags, key->bit_nr);
+
+ if (key->flags != &page->flags ||
+ key->bit_nr != PG_locked)
+ return 0;
+
+ _debug("--- monitor %p %lx ---", page, page->flags);
+
+ if (!PageUptodate(page) && !PageError(page))
+ dump_stack();
+
+ /* remove from the waitqueue */
+ list_del(&wait->task_list);
+
+ /* move onto the action list and queue for FS-Cache thread pool */
+ ASSERT(monitor->op);
+
+ object = container_of(monitor->op->op.object,
+ struct cachefiles_object, fscache);
+
+ spin_lock(&object->work_lock);
+ list_add(&monitor->op_link, &monitor->op->to_do);
+ spin_unlock(&object->work_lock);
+
+ fscache_enqueue_retrieval(monitor->op);
+ return 0;
+}
+
+/*
+ * copy data from backing pages to netfs pages to complete a read operation
+ * - driven by FS-Cache's thread pool
+ */
+static void cachefiles_read_copier(struct fscache_operation *_op)
+{
+ struct cachefiles_one_read *monitor;
+ struct cachefiles_object *object;
+ struct fscache_retrieval *op;
+ struct pagevec pagevec;
+ int error, max;
+
+ op = container_of(_op, struct fscache_retrieval, op);
+ object = container_of(op->op.object,
+ struct cachefiles_object, fscache);
+
+ _enter("{ino=%lu}", object->backer->d_inode->i_ino);
+
+ pagevec_init(&pagevec, 0);
+
+ max = 8;
+ spin_lock_irq(&object->work_lock);
+
+ while (!list_empty(&op->to_do)) {
+ monitor = list_entry(op->to_do.next,
+ struct cachefiles_one_read, op_link);
+ list_del(&monitor->op_link);
+
+ spin_unlock_irq(&object->work_lock);
+
+ _debug("- copy {%lu}", monitor->back_page->index);
+
+ error = -EIO;
+ if (PageUptodate(monitor->back_page)) {
+ copy_highpage(monitor->netfs_page, monitor->back_page);
+
+ pagevec_add(&pagevec, monitor->netfs_page);
+ fscache_mark_pages_cached(monitor->op, &pagevec);
+ error = 0;
+ }
+
+ if (error)
+ cachefiles_io_error_obj(
+ object,
+ "Readpage failed on backing file %lx",
+ (unsigned long) monitor->back_page->flags);
+
+ page_cache_release(monitor->back_page);
+
+ fscache_end_io(op, monitor->netfs_page, error);
+ page_cache_release(monitor->netfs_page);
+ fscache_put_retrieval(op);
+ kfree(monitor);
+
+ /* let the thread pool have some air occasionally */
+ max--;
+ if (max < 0 || need_resched()) {
+ if (!list_empty(&op->to_do))
+ fscache_enqueue_retrieval(op);
+ _leave(" [maxed out]");
+ return;
+ }
+
+ spin_lock_irq(&object->work_lock);
+ }
+
+ spin_unlock_irq(&object->work_lock);
+ _leave("");
+}
+
+/*
+ * read the corresponding page to the given set from the backing file
+ * - an uncertain page is simply discarded, to be tried again another time
+ */
+static int cachefiles_read_backing_file_one(struct cachefiles_object *object,
+ struct fscache_retrieval *op,
+ struct page *netpage,
+ struct pagevec *pagevec)
+{
+ struct cachefiles_one_read *monitor;
+ struct address_space *bmapping;
+ struct page *newpage, *backpage;
+ int ret;
+
+ _enter("");
+
+ pagevec_reinit(pagevec);
+
+ _debug("read back %p{%lu,%d}",
+ netpage, netpage->index, page_count(netpage));
+
+ monitor = kzalloc(sizeof(*monitor), GFP_KERNEL);
+ if (!monitor)
+ goto nomem;
+
+ monitor->netfs_page = netpage;
+ monitor->op = fscache_get_retrieval(op);
+
+ init_waitqueue_func_entry(&monitor->monitor, cachefiles_read_waiter);
+
+ /* attempt to get hold of the backing page */
+ bmapping = object->backer->d_inode->i_mapping;
+ newpage = NULL;
+
+ for (;;) {
+ backpage = find_get_page(bmapping, netpage->index);
+ if (backpage)
+ goto backing_page_already_present;
+
+ if (!newpage) {
+ newpage = page_cache_alloc_cold(bmapping);
+ if (!newpage)
+ goto nomem_monitor;
+ }
+
+ ret = add_to_page_cache(newpage, bmapping,
+ netpage->index, GFP_KERNEL);
+ if (ret == 0)
+ goto installed_new_backing_page;
+ if (ret != -EEXIST)
+ goto nomem_page;
+ }
+
+ /* we've installed a new backing page, so now we need to add it
+ * to the LRU list and start it reading */
+installed_new_backing_page:
+ _debug("- new %p", newpage);
+
+ backpage = newpage;
+ newpage = NULL;
+
+ page_cache_get(backpage);
+ pagevec_add(pagevec, backpage);
+ __pagevec_lru_add(pagevec);
+
+read_backing_page:
+ ret = bmapping->a_ops->readpage(NULL, backpage);
+ if (ret < 0)
+ goto read_error;
+
+ /* set the monitor to transfer the data across */
+monitor_backing_page:
+ _debug("- monitor add");
+
+ /* install the monitor */
+ page_cache_get(monitor->netfs_page);
+ page_cache_get(backpage);
+ monitor->back_page = backpage;
+ monitor->monitor.private = backpage;
+ add_page_wait_queue(backpage, &monitor->monitor);
+ monitor = NULL;
+
+ /* but the page may have been read before the monitor was installed, so
+ * the monitor may miss the event - so we have to ensure that we do get
+ * one in such a case */
+ if (!TestSetPageLocked(backpage)) {
+ _debug("jumpstart %p {%lx}", backpage, backpage->flags);
+ unlock_page(backpage);
+ }
+ goto success;
+
+ /* if the backing page is already present, it can be in one of
+ * three states: read in progress, read failed or read okay */
+backing_page_already_present:
+ _debug("- present");
+
+ if (newpage) {
+ page_cache_release(newpage);
+ newpage = NULL;
+ }
+
+ if (PageError(backpage))
+ goto io_error;
+
+ if (PageUptodate(backpage))
+ goto backing_page_already_uptodate;
+
+ if (TestSetPageLocked(backpage))
+ goto monitor_backing_page;
+ _debug("read %p {%lx}", backpage, backpage->flags);
+ goto read_backing_page;
+
+ /* the backing page is already up to date, attach the netfs
+ * page to the pagecache and LRU and copy the data across */
+backing_page_already_uptodate:
+ _debug("- uptodate");
+
+ pagevec_add(pagevec, netpage);
+ fscache_mark_pages_cached(op, pagevec);
+
+ copy_highpage(netpage, backpage);
+ fscache_end_io(op, netpage, 0);
+
+success:
+ _debug("success");
+ ret = 0;
+
+out:
+ if (backpage)
+ page_cache_release(backpage);
+ if (monitor) {
+ fscache_put_retrieval(monitor->op);
+ kfree(monitor);
+ }
+ _leave(" = %d", ret);
+ return ret;
+
+read_error:
+ _debug("read error %d", ret);
+ if (ret == -ENOMEM)
+ goto out;
+io_error:
+ cachefiles_io_error_obj(object, "Page read error on backing file");
+ ret = -ENOBUFS;
+ goto out;
+
+nomem_page:
+ page_cache_release(newpage);
+nomem_monitor:
+ fscache_put_retrieval(monitor->op);
+ kfree(monitor);
+nomem:
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+}
+
+/*
+ * read a page from the cache or allocate a block in which to store it
+ * - cache withdrawal is prevented by the caller
+ * - returns -EINTR if interrupted
+ * - returns -ENOMEM if ran out of memory
+ * - returns -ENOBUFS if no buffers can be made available
+ * - returns -ENOBUFS if page is beyond EOF
+ * - if the page is backed by a block in the cache:
+ * - a read will be started which will call the callback on completion
+ * - 0 will be returned
+ * - else if the page is unbacked:
+ * - the metadata will be retained
+ * - -ENODATA will be returned
+ */
+int cachefiles_read_or_alloc_page(struct fscache_retrieval *op,
+ struct page *page,
+ gfp_t gfp)
+{
+ struct cachefiles_object *object;
+ struct cachefiles_cache *cache;
+ struct pagevec pagevec;
+ struct inode *inode;
+ sector_t block0, block;
+ unsigned shift;
+ int ret;
+
+ object = container_of(op->op.object,
+ struct cachefiles_object, fscache);
+ cache = container_of(object->fscache.cache,
+ struct cachefiles_cache, cache);
+
+ _enter("{%p},{%lx},,,", object, page->index);
+
+ if (!object->backer)
+ return -ENOBUFS;
+
+ inode = object->backer->d_inode;
+ ASSERT(S_ISREG(inode->i_mode));
+ ASSERT(inode->i_mapping->a_ops->bmap);
+ ASSERT(inode->i_mapping->a_ops->readpages);
+
+ /* calculate the shift required to use bmap */
+ if (inode->i_sb->s_blocksize > PAGE_SIZE)
+ return -ENOBUFS;
+
+ shift = PAGE_SHIFT - inode->i_sb->s_blocksize_bits;
+
+ op->op.processor = cachefiles_read_copier;
+
+ pagevec_init(&pagevec, 0);
+
+ /* we assume the absence or presence of the first block is a good
+ * enough indication for the page as a whole
+ * - TODO: don't use bmap() for this as it is _not_ actually good
+ * enough for this as it doesn't indicate errors, but it's all we've
+ * got for the moment
+ */
+ block0 = page->index;
+ block0 <<= shift;
+
+ block = inode->i_mapping->a_ops->bmap(inode->i_mapping, block0);
+ _debug("%llx -> %llx",
+ (unsigned long long) block0,
+ (unsigned long long) block);
+
+ if (block) {
+ /* submit the apparently valid page to the backing fs to be
+ * read from disk */
+ ret = cachefiles_read_backing_file_one(object, op, page,
+ &pagevec);
+ } else if (cachefiles_has_space(cache, 0, 1) == 0) {
+ /* there's space in the cache we can use */
+ pagevec_add(&pagevec, page);
+ fscache_mark_pages_cached(op, &pagevec);
+ ret = -ENODATA;
+ } else {
+ ret = -ENOBUFS;
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * read the corresponding pages to the given set from the backing file
+ * - any uncertain pages are simply discarded, to be tried again another time
+ */
+static int cachefiles_read_backing_file(struct cachefiles_object *object,
+ struct fscache_retrieval *op,
+ struct list_head *list,
+ struct pagevec *mark_pvec)
+{
+ struct cachefiles_one_read *monitor = NULL;
+ struct address_space *bmapping = object->backer->d_inode->i_mapping;
+ struct pagevec lru_pvec;
+ struct page *newpage = NULL, *netpage, *_n, *backpage = NULL;
+ int ret = 0;
+
+ _enter("");
+
+ pagevec_init(&lru_pvec, 0);
+
+ list_for_each_entry_safe(netpage, _n, list, lru) {
+ list_del(&netpage->lru);
+
+ _debug("read back %p{%lu,%d}",
+ netpage, netpage->index, page_count(netpage));
+
+ if (!monitor) {
+ monitor = kzalloc(sizeof(*monitor), GFP_KERNEL);
+ if (!monitor)
+ goto nomem;
+
+ monitor->op = fscache_get_retrieval(op);
+ init_waitqueue_func_entry(&monitor->monitor,
+ cachefiles_read_waiter);
+ }
+
+ for (;;) {
+ backpage = find_get_page(bmapping, netpage->index);
+ if (backpage)
+ goto backing_page_already_present;
+
+ if (!newpage) {
+ newpage = page_cache_alloc_cold(bmapping);
+ if (!newpage)
+ goto nomem;
+ }
+
+ ret = add_to_page_cache(newpage, bmapping,
+ netpage->index, GFP_KERNEL);
+ if (ret == 0)
+ goto installed_new_backing_page;
+ if (ret != -EEXIST)
+ goto nomem;
+ }
+
+ /* we've installed a new backing page, so now we need to add it
+ * to the LRU list and start it reading */
+ installed_new_backing_page:
+ _debug("- new %p", newpage);
+
+ backpage = newpage;
+ newpage = NULL;
+
+ page_cache_get(backpage);
+ if (!pagevec_add(&lru_pvec, backpage))
+ __pagevec_lru_add(&lru_pvec);
+
+ reread_backing_page:
+ ret = bmapping->a_ops->readpage(NULL, backpage);
+ if (ret < 0)
+ goto read_error;
+
+ /* add the netfs page to the pagecache and LRU, and set the
+ * monitor to transfer the data across */
+ monitor_backing_page:
+ _debug("- monitor add");
+
+ ret = add_to_page_cache(netpage, op->mapping, netpage->index,
+ GFP_KERNEL);
+ if (ret < 0) {
+ if (ret == -EEXIST) {
+ page_cache_release(netpage);
+ continue;
+ }
+ goto nomem;
+ }
+
+ page_cache_get(netpage);
+ if (!pagevec_add(&lru_pvec, netpage))
+ __pagevec_lru_add(&lru_pvec);
+
+ /* install a monitor */
+ page_cache_get(netpage);
+ monitor->netfs_page = netpage;
+
+ page_cache_get(backpage);
+ monitor->back_page = backpage;
+ monitor->monitor.private = backpage;
+ add_page_wait_queue(backpage, &monitor->monitor);
+ monitor = NULL;
+
+ /* but the page may have been read before the monitor was
+ * installed, so the monitor may miss the event - so we have to
+ * ensure that we do get one in such a case */
+ if (!TestSetPageLocked(backpage)) {
+ _debug("2unlock %p {%lx}", backpage, backpage->flags);
+ unlock_page(backpage);
+ }
+
+ page_cache_release(backpage);
+ backpage = NULL;
+
+ page_cache_release(netpage);
+ netpage = NULL;
+ continue;
+
+ /* if the backing page is already present, it can be in one of
+ * three states: read in progress, read failed or read okay */
+ backing_page_already_present:
+ _debug("- present %p", backpage);
+
+ if (PageError(backpage))
+ goto io_error;
+
+ if (PageUptodate(backpage))
+ goto backing_page_already_uptodate;
+
+ _debug("- not ready %p{%lx}", backpage, backpage->flags);
+
+ if (TestSetPageLocked(backpage))
+ goto monitor_backing_page;
+
+ if (PageError(backpage)) {
+ _debug("error %lx", backpage->flags);
+ unlock_page(backpage);
+ goto io_error;
+ }
+
+ if (PageUptodate(backpage))
+ goto backing_page_already_uptodate_unlock;
+
+ /* we've locked a page that's neither up to date nor erroneous,
+ * so we need to attempt to read it again */
+ goto reread_backing_page;
+
+ /* the backing page is already up to date, attach the netfs
+ * page to the pagecache and LRU and copy the data across */
+ backing_page_already_uptodate_unlock:
+ _debug("uptodate %lx", backpage->flags);
+ unlock_page(backpage);
+ backing_page_already_uptodate:
+ _debug("- uptodate");
+
+ ret = add_to_page_cache(netpage, op->mapping, netpage->index,
+ GFP_KERNEL);
+ if (ret < 0) {
+ if (ret == -EEXIST) {
+ page_cache_release(netpage);
+ continue;
+ }
+ goto nomem;
+ }
+
+ copy_highpage(netpage, backpage);
+
+ page_cache_release(backpage);
+ backpage = NULL;
+
+ if (!pagevec_add(mark_pvec, netpage))
+ fscache_mark_pages_cached(op, mark_pvec);
+
+ page_cache_get(netpage);
+ if (!pagevec_add(&lru_pvec, netpage))
+ __pagevec_lru_add(&lru_pvec);
+
+ fscache_end_io(op, netpage, 0);
+ page_cache_release(netpage);
+ netpage = NULL;
+ continue;
+ }
+
+ netpage = NULL;
+
+ _debug("out");
+
+out:
+ /* tidy up */
+ pagevec_lru_add(&lru_pvec);
+
+ if (newpage)
+ page_cache_release(newpage);
+ if (netpage)
+ page_cache_release(netpage);
+ if (backpage)
+ page_cache_release(backpage);
+ if (monitor) {
+ fscache_put_retrieval(op);
+ kfree(monitor);
+ }
+
+ list_for_each_entry_safe(netpage, _n, list, lru) {
+ list_del(&netpage->lru);
+ page_cache_release(netpage);
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+
+nomem:
+ _debug("nomem");
+ ret = -ENOMEM;
+ goto out;
+
+read_error:
+ _debug("read error %d", ret);
+ if (ret == -ENOMEM)
+ goto out;
+io_error:
+ cachefiles_io_error_obj(object, "Page read error on backing file");
+ ret = -ENOBUFS;
+ goto out;
+}
+
+/*
+ * read a list of pages from the cache or allocate blocks in which to store
+ * them
+ */
+int cachefiles_read_or_alloc_pages(struct fscache_retrieval *op,
+ struct list_head *pages,
+ unsigned *nr_pages,
+ gfp_t gfp)
+{
+ struct cachefiles_object *object;
+ struct cachefiles_cache *cache;
+ struct list_head backpages;
+ struct pagevec pagevec;
+ struct inode *inode;
+ struct page *page, *_n;
+ unsigned shift, nrbackpages;
+ int ret, ret2, space;
+
+ object = container_of(op->op.object,
+ struct cachefiles_object, fscache);
+ cache = container_of(object->fscache.cache,
+ struct cachefiles_cache, cache);
+
+ _enter("{OBJ%x,%d},,%d,,",
+ object->fscache.debug_id, atomic_read(&op->op.usage),
+ *nr_pages);
+
+ if (!object->backer)
+ return -ENOBUFS;
+
+ space = 1;
+ if (cachefiles_has_space(cache, 0, *nr_pages) < 0)
+ space = 0;
+
+ inode = object->backer->d_inode;
+ ASSERT(S_ISREG(inode->i_mode));
+ ASSERT(inode->i_mapping->a_ops->bmap);
+ ASSERT(inode->i_mapping->a_ops->readpages);
+
+ /* calculate the shift required to use bmap */
+ if (inode->i_sb->s_blocksize > PAGE_SIZE)
+ return -ENOBUFS;
+
+ shift = PAGE_SHIFT - inode->i_sb->s_blocksize_bits;
+
+ pagevec_init(&pagevec, 0);
+
+ op->op.processor = cachefiles_read_copier;
+
+ INIT_LIST_HEAD(&backpages);
+ nrbackpages = 0;
+
+ ret = space ? -ENODATA : -ENOBUFS;
+ list_for_each_entry_safe(page, _n, pages, lru) {
+ sector_t block0, block;
+
+ /* we assume the absence or presence of the first block is a
+ * good enough indication for the page as a whole
+ * - TODO: don't use bmap() for this as it is _not_ actually
+ * good enough for this as it doesn't indicate errors, but
+ * it's all we've got for the moment
+ */
+ block0 = page->index;
+ block0 <<= shift;
+
+ block = inode->i_mapping->a_ops->bmap(inode->i_mapping,
+ block0);
+ _debug("%llx -> %llx",
+ (unsigned long long) block0,
+ (unsigned long long) block);
+
+ if (block) {
+ /* we have data - add it to the list to give to the
+ * backing fs */
+ list_move(&page->lru, &backpages);
+ (*nr_pages)--;
+ nrbackpages++;
+ } else if (space && pagevec_add(&pagevec, page) == 0) {
+ fscache_mark_pages_cached(op, &pagevec);
+ ret = -ENODATA;
+ }
+ }
+
+ if (pagevec_count(&pagevec) > 0)
+ fscache_mark_pages_cached(op, &pagevec);
+
+ if (list_empty(pages))
+ ret = 0;
+
+ /* submit the apparently valid pages to the backing fs to be read from
+ * disk */
+ if (nrbackpages > 0) {
+ ret2 = cachefiles_read_backing_file(object, op, &backpages,
+ &pagevec);
+ if (ret2 == -ENOMEM || ret2 == -EINTR)
+ ret = ret2;
+ }
+
+ if (pagevec_count(&pagevec) > 0)
+ fscache_mark_pages_cached(op, &pagevec);
+
+ _leave(" = %d [nr=%u%s]",
+ ret, *nr_pages, list_empty(pages) ? " empty" : "");
+ return ret;
+}
+
+/*
+ * allocate a block in the cache in which to store a page
+ * - cache withdrawal is prevented by the caller
+ * - returns -EINTR if interrupted
+ * - returns -ENOMEM if ran out of memory
+ * - returns -ENOBUFS if no buffers can be made available
+ * - returns -ENOBUFS if page is beyond EOF
+ * - otherwise:
+ * - the metadata will be retained
+ * - 0 will be returned
+ */
+int cachefiles_allocate_page(struct fscache_retrieval *op,
+ struct page *page,
+ gfp_t gfp)
+{
+ struct cachefiles_object *object;
+ struct cachefiles_cache *cache;
+ struct pagevec pagevec;
+ int ret;
+
+ object = container_of(op->op.object,
+ struct cachefiles_object, fscache);
+ cache = container_of(object->fscache.cache,
+ struct cachefiles_cache, cache);
+
+ _enter("%p,{%lx},", object, page->index);
+
+ ret = cachefiles_has_space(cache, 0, 1);
+ if (ret == 0) {
+ pagevec_init(&pagevec, 0);
+ pagevec_add(&pagevec, page);
+ fscache_mark_pages_cached(op, &pagevec);
+ } else {
+ ret = -ENOBUFS;
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * allocate blocks in the cache in which to store a set of pages
+ * - cache withdrawal is prevented by the caller
+ * - returns -EINTR if interrupted
+ * - returns -ENOMEM if ran out of memory
+ * - returns -ENOBUFS if some buffers couldn't be made available
+ * - returns -ENOBUFS if some pages are beyond EOF
+ * - otherwise:
+ * - -ENODATA will be returned
+ * - metadata will be retained for any page marked
+ */
+int cachefiles_allocate_pages(struct fscache_retrieval *op,
+ struct list_head *pages,
+ unsigned *nr_pages,
+ gfp_t gfp)
+{
+ struct cachefiles_object *object;
+ struct cachefiles_cache *cache;
+ struct pagevec pagevec;
+ struct page *page;
+ int ret;
+
+ object = container_of(op->op.object,
+ struct cachefiles_object, fscache);
+ cache = container_of(object->fscache.cache,
+ struct cachefiles_cache, cache);
+
+ _enter("%p,,,%d,", object, *nr_pages);
+
+ ret = cachefiles_has_space(cache, 0, *nr_pages);
+ if (ret == 0) {
+ pagevec_init(&pagevec, 0);
+
+ list_for_each_entry(page, pages, lru) {
+ if (pagevec_add(&pagevec, page) == 0)
+ fscache_mark_pages_cached(op, &pagevec);
+ }
+
+ if (pagevec_count(&pagevec) > 0)
+ fscache_mark_pages_cached(op, &pagevec);
+ ret = -ENODATA;
+ } else {
+ ret = -ENOBUFS;
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * request a page be stored in the cache
+ * - cache withdrawal is prevented by the caller
+ * - this request may be ignored if there's no cache block available, in which
+ * case -ENOBUFS will be returned
+ * - if the op is in progress, 0 will be returned
+ */
+int cachefiles_write_page(struct fscache_storage *op, struct page *page)
+{
+ struct cachefiles_object *object;
+ struct address_space *mapping;
+ int ret;
+
+ ASSERT(op != NULL);
+ ASSERT(page != NULL);
+
+ object = container_of(op->op.object,
+ struct cachefiles_object, fscache);
+
+ _enter("%p,%p{%lx},,,", object, page, page->index);
+
+ if (!object->backer) {
+ _leave(" = -ENOBUFS");
+ return -ENOBUFS;
+ }
+
+ ASSERT(S_ISREG(object->backer->d_inode->i_mode));
+
+ /* copy the page to ext3 and let it store it in its own time */
+ mapping = object->backer->d_inode->i_mapping;
+ ret = -EIO;
+ if (mapping->a_ops->write_one_page)
+ ret = mapping->a_ops->write_one_page(mapping, page->index,
+ page);
+
+ if (ret != 0) {
+ if (ret == -EIO)
+ cachefiles_io_error_obj(
+ object, "Write page to backing file failed");
+ ret = -ENOBUFS;
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * detach a backing block from a page
+ * - cache withdrawal is prevented by the caller
+ */
+void cachefiles_uncache_page(struct fscache_object *_object, struct page *page)
+{
+ struct cachefiles_object *object;
+ struct cachefiles_cache *cache;
+
+ object = container_of(_object, struct cachefiles_object, fscache);
+ cache = container_of(object->fscache.cache,
+ struct cachefiles_cache, cache);
+
+ _enter("%p,{%lu}", object, page->index);
+
+ spin_unlock(&object->fscache.cookie->lock);
+}
diff --git a/fs/cachefiles/cf-security.c b/fs/cachefiles/cf-security.c
new file mode 100644
index 0000000..65154b8
--- /dev/null
+++ b/fs/cachefiles/cf-security.c
@@ -0,0 +1,94 @@
+/* CacheFiles security management
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/fs.h>
+#include "cf-internal.h"
+
+/*
+ * determine the security context within which we access the cache from within
+ * the kernel
+ */
+int cachefiles_get_security_ID(struct cachefiles_cache *cache)
+{
+ struct cred *cred;
+ int ret;
+
+ _enter("");
+
+ cred = get_kernel_cred("cachefiles", current);
+ if (IS_ERR(cred)) {
+ ret = PTR_ERR(cred);
+ goto error;
+ }
+
+ cache->cache_cred = cred;
+ ret = 0;
+error:
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * check the security details of the on-disk cache
+ * - must be called with security imposed
+ */
+int cachefiles_determine_cache_secid(struct cachefiles_cache *cache,
+ struct dentry *root)
+{
+ struct cred *cred, *saved_cred;
+ int ret;
+
+ _enter("");
+
+ /* the cache creds are in use already, so we have to alter a copy */
+ cred = dup_cred(cache->cache_cred);
+ if (!cred)
+ return -ENOMEM;
+
+ /* use the cache root dir's security context as the basis with which
+ * create files */
+ ret = change_create_files_as(cred, root->d_inode);
+ if (ret < 0) {
+ put_cred(cred);
+ _leave(" = %d [cfa]", ret);
+ return ret;
+ }
+
+ put_cred(cache->cache_cred);
+ cache->cache_cred = cred;
+
+ /* check that we have permission to create files and directories with
+ * the security ID we've been given */
+ cachefiles_begin_secure(cache, &saved_cred);
+
+ ret = security_inode_mkdir(root->d_inode, root, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "CacheFiles:"
+ " Security denies permission to make dirs: error %d",
+ ret);
+ goto error;
+ }
+
+ ret = security_inode_create(root->d_inode, root, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "CacheFiles:"
+ " Security denies permission to create files: error %d",
+ ret);
+ goto error;
+ }
+
+error:
+ cachefiles_end_secure(cache, saved_cred);
+ if (ret == -EOPNOTSUPP)
+ ret = 0;
+ _leave(" = %d", ret);
+ return ret;
+}
diff --git a/fs/cachefiles/cf-xattr.c b/fs/cachefiles/cf-xattr.c
new file mode 100644
index 0000000..9ae9240
--- /dev/null
+++ b/fs/cachefiles/cf-xattr.c
@@ -0,0 +1,292 @@
+/* CacheFiles extended attribute management
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/fsnotify.h>
+#include <linux/quotaops.h>
+#include <linux/xattr.h>
+#include "cf-internal.h"
+
+static const char cachefiles_xattr_cache[] =
+ XATTR_USER_PREFIX "CacheFiles.cache";
+
+/*
+ * check the type label on an object
+ * - done using xattrs
+ */
+int cachefiles_check_object_type(struct cachefiles_object *object)
+{
+ struct dentry *dentry = object->dentry;
+ char type[3], xtype[3];
+ int ret;
+
+ ASSERT(dentry);
+ ASSERT(dentry->d_inode);
+
+ if (!object->fscache.cookie)
+ strcpy(type, "C3");
+ else
+ snprintf(type, 3, "%02x", object->fscache.cookie->def->type);
+
+ _enter("%p{%s}", object, type);
+
+ /* attempt to install a type label directly */
+ ret = vfs_setxattr(dentry, cachefiles_xattr_cache, type, 2,
+ XATTR_CREATE);
+ if (ret == 0) {
+ _debug("SET"); /* we succeeded */
+ goto error;
+ }
+
+ if (ret != -EEXIST) {
+ kerror("Can't set xattr on %*.*s [%lu] (err %d)",
+ dentry->d_name.len, dentry->d_name.len,
+ dentry->d_name.name, dentry->d_inode->i_ino,
+ -ret);
+ goto error;
+ }
+
+ /* read the current type label */
+ ret = vfs_getxattr(dentry, cachefiles_xattr_cache, xtype, 3);
+ if (ret < 0) {
+ if (ret == -ERANGE)
+ goto bad_type_length;
+
+ kerror("Can't read xattr on %*.*s [%lu] (err %d)",
+ dentry->d_name.len, dentry->d_name.len,
+ dentry->d_name.name, dentry->d_inode->i_ino,
+ -ret);
+ goto error;
+ }
+
+ /* check the type is what we're expecting */
+ if (ret != 2)
+ goto bad_type_length;
+
+ if (xtype[0] != type[0] || xtype[1] != type[1])
+ goto bad_type;
+
+ ret = 0;
+
+error:
+ _leave(" = %d", ret);
+ return ret;
+
+bad_type_length:
+ kerror("Cache object %lu type xattr length incorrect",
+ dentry->d_inode->i_ino);
+ ret = -EIO;
+ goto error;
+
+bad_type:
+ xtype[2] = 0;
+ kerror("Cache object %*.*s [%lu] type %s not %s",
+ dentry->d_name.len, dentry->d_name.len,
+ dentry->d_name.name, dentry->d_inode->i_ino,
+ xtype, type);
+ ret = -EIO;
+ goto error;
+}
+
+/*
+ * set the state xattr on a cache file
+ */
+int cachefiles_set_object_xattr(struct cachefiles_object *object,
+ struct cachefiles_xattr *auxdata)
+{
+ struct dentry *dentry = object->dentry;
+ int ret;
+
+ ASSERT(object->fscache.cookie);
+ ASSERT(dentry);
+
+ _enter("%p,#%d", object, auxdata->len);
+
+ /* attempt to install the cache metadata directly */
+ _debug("SET %s #%u", object->fscache.cookie->def->name, auxdata->len);
+
+ ret = vfs_setxattr(dentry, cachefiles_xattr_cache,
+ &auxdata->type, auxdata->len,
+ XATTR_CREATE);
+ if (ret < 0 && ret != -ENOMEM)
+ cachefiles_io_error_obj(
+ object,
+ "Failed to set xattr with error %d", ret);
+
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * update the state xattr on a cache file
+ */
+int cachefiles_update_object_xattr(struct cachefiles_object *object,
+ struct cachefiles_xattr *auxdata)
+{
+ struct dentry *dentry = object->dentry;
+ int ret;
+
+ ASSERT(object->fscache.cookie);
+ ASSERT(dentry);
+
+ _enter("%p,#%d", object, auxdata->len);
+
+ /* attempt to install the cache metadata directly */
+ _debug("SET %s #%u", object->fscache.cookie->def->name, auxdata->len);
+
+ ret = vfs_setxattr(dentry, cachefiles_xattr_cache,
+ &auxdata->type, auxdata->len,
+ XATTR_REPLACE);
+ if (ret < 0 && ret != -ENOMEM)
+ cachefiles_io_error_obj(
+ object,
+ "Failed to update xattr with error %d", ret);
+
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * check the state xattr on a cache file
+ * - return -ESTALE if the object should be deleted
+ */
+int cachefiles_check_object_xattr(struct cachefiles_object *object,
+ struct cachefiles_xattr *auxdata)
+{
+ struct cachefiles_xattr *auxbuf;
+ struct dentry *dentry = object->dentry;
+ int ret;
+
+ _enter("%p,#%d", object, auxdata->len);
+
+ ASSERT(dentry);
+ ASSERT(dentry->d_inode);
+
+ auxbuf = kmalloc(sizeof(struct cachefiles_xattr) + 512, GFP_KERNEL);
+ if (!auxbuf) {
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+ }
+
+ /* read the current type label */
+ ret = vfs_getxattr(dentry, cachefiles_xattr_cache,
+ &auxbuf->type, 512 + 1);
+ if (ret < 0) {
+ if (ret == -ENODATA)
+ goto stale; /* no attribute - power went off
+ * mid-cull? */
+
+ if (ret == -ERANGE)
+ goto bad_type_length;
+
+ cachefiles_io_error_obj(object,
+ "Can't read xattr on %lu (err %d)",
+ dentry->d_inode->i_ino, -ret);
+ goto error;
+ }
+
+ /* check the on-disk object */
+ if (ret < 1)
+ goto bad_type_length;
+
+ if (auxbuf->type != auxdata->type)
+ goto stale;
+
+ auxbuf->len = ret;
+
+ /* consult the netfs */
+ if (object->fscache.cookie->def->check_aux) {
+ fscache_checkaux_t result;
+ unsigned int dlen;
+
+ dlen = auxbuf->len - 1;
+
+ _debug("checkaux %s #%u",
+ object->fscache.cookie->def->name, dlen);
+
+ result = object->fscache.cookie->def->check_aux(
+ object->fscache.cookie->netfs_data,
+ &auxbuf->data, dlen);
+
+ switch (result) {
+ /* entry okay as is */
+ case FSCACHE_CHECKAUX_OKAY:
+ goto okay;
+
+ /* entry requires update */
+ case FSCACHE_CHECKAUX_NEEDS_UPDATE:
+ break;
+
+ /* entry requires deletion */
+ case FSCACHE_CHECKAUX_OBSOLETE:
+ goto stale;
+
+ default:
+ BUG();
+ }
+
+ /* update the current label */
+ ret = vfs_setxattr(dentry, cachefiles_xattr_cache,
+ &auxdata->type, auxdata->len,
+ XATTR_REPLACE);
+ if (ret < 0) {
+ cachefiles_io_error_obj(object,
+ "Can't update xattr on %lu"
+ " (error %d)",
+ dentry->d_inode->i_ino, -ret);
+ goto error;
+ }
+ }
+
+okay:
+ ret = 0;
+
+error:
+ kfree(auxbuf);
+ _leave(" = %d", ret);
+ return ret;
+
+bad_type_length:
+ kerror("Cache object %lu xattr length incorrect",
+ dentry->d_inode->i_ino);
+ ret = -EIO;
+ goto error;
+
+stale:
+ ret = -ESTALE;
+ goto error;
+}
+
+/*
+ * remove the object's xattr to mark it stale
+ */
+int cachefiles_remove_object_xattr(struct cachefiles_cache *cache,
+ struct dentry *dentry)
+{
+ int ret;
+
+ ret = vfs_removexattr(dentry, cachefiles_xattr_cache);
+ if (ret < 0) {
+ if (ret == -ENOENT || ret == -ENODATA)
+ ret = 0;
+ else if (ret != -ENOMEM)
+ cachefiles_io_error(cache,
+ "Can't remove xattr from %lu"
+ " (error %d)",
+ dentry->d_inode->i_ino, -ret);
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+}
Implement shared-writable mmap for AFS.
The key with which to access the file is obtained from the VMA at the point
where the PTE is made writable by the page_mkwrite() VMA op and cached in the
affected page.
If there's an outstanding write on the page made with a different key, then
page_mkwrite() will flush it before attaching a record of the new key.
Signed-off-by: David Howells <[email protected]>
---
fs/afs/file.c | 20 +++++++++++++++++++-
fs/afs/internal.h | 1 +
fs/afs/write.c | 35 +++++++++++++++++++++++++++++++++++
3 files changed, 55 insertions(+), 1 deletions(-)
diff --git a/fs/afs/file.c b/fs/afs/file.c
index 525f7c5..1323df4 100644
--- a/fs/afs/file.c
+++ b/fs/afs/file.c
@@ -22,6 +22,7 @@ static int afs_readpage(struct file *file, struct page *page);
static void afs_invalidatepage(struct page *page, unsigned long offset);
static int afs_releasepage(struct page *page, gfp_t gfp_flags);
static int afs_launder_page(struct page *page);
+static int afs_mmap(struct file *file, struct vm_area_struct *vma);
const struct file_operations afs_file_operations = {
.open = afs_open,
@@ -31,7 +32,7 @@ const struct file_operations afs_file_operations = {
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = afs_file_write,
- .mmap = generic_file_readonly_mmap,
+ .mmap = afs_mmap,
.splice_read = generic_file_splice_read,
.fsync = afs_fsync,
.lock = afs_lock,
@@ -56,6 +57,11 @@ const struct address_space_operations afs_fs_aops = {
.writepages = afs_writepages,
};
+static struct vm_operations_struct afs_file_vm_ops = {
+ .fault = filemap_fault,
+ .page_mkwrite = afs_page_mkwrite,
+};
+
/*
* open an AFS file or directory and attach a key to it
*/
@@ -295,3 +301,15 @@ static int afs_releasepage(struct page *page, gfp_t gfp_flags)
_leave(" = 0");
return 0;
}
+
+/*
+ * memory map part of an AFS file
+ */
+static int afs_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ _enter("");
+
+ file_accessed(file);
+ vma->vm_ops = &afs_file_vm_ops;
+ return 0;
+}
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index e1bcce0..12afccc 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -743,6 +743,7 @@ extern ssize_t afs_file_write(struct kiocb *, const struct iovec *,
unsigned long, loff_t);
extern int afs_writeback_all(struct afs_vnode *);
extern int afs_fsync(struct file *, struct dentry *, int);
+extern int afs_page_mkwrite(struct vm_area_struct *, struct page *);
/*****************************************************************************/
diff --git a/fs/afs/write.c b/fs/afs/write.c
index ac621e8..dd471f0 100644
--- a/fs/afs/write.c
+++ b/fs/afs/write.c
@@ -155,6 +155,8 @@ static int afs_prepare_page(struct afs_vnode *vnode, struct page *page,
* prepare to perform part of a write to a page
* - the caller holds the page locked, preventing it from being written out or
* modified by anyone else
+ * - may be called from afs_page_mkwrite() to set up a page for modification
+ * through shared-writable mmap
*/
int afs_prepare_write(struct file *file, struct page *page,
unsigned offset, unsigned to)
@@ -833,3 +835,36 @@ int afs_fsync(struct file *file, struct dentry *dentry, int datasync)
_leave(" = %d", ret);
return ret;
}
+
+/*
+ * notification that a previously read-only page is about to become writable
+ * - if it returns an error, the caller will deliver a bus error signal
+ *
+ * we use this to make a record of the key with which the writeback should be
+ * performed and to flush any outstanding writes made with a different key
+ *
+ * the key to be used is attached to the struct file pinned by the VMA
+ */
+int afs_page_mkwrite(struct vm_area_struct *vma, struct page *page)
+{
+ struct afs_vnode *vnode = AFS_FS_I(vma->vm_file->f_mapping->host);
+ struct key *key = vma->vm_file->private_data;
+ int ret;
+
+ _enter("{{%x:%u},%x},{%lx}",
+ vnode->fid.vid, vnode->fid.vnode, key_serial(key), page->index);
+
+ do {
+ lock_page(page);
+ if (page->mapping == vma->vm_file->f_mapping)
+ ret = afs_prepare_write(vma->vm_file, page, 0,
+ PAGE_SIZE);
+ else
+ ret = 0; /* seems there was interference - let the
+ * caller deal with it */
+ unlock_page(page);
+ } while (ret == AOP_TRUNCATED_PAGE);
+
+ _leave(" = %d", ret);
+ return ret;
+}
Save the operation ID to be used with a call that we're making for display
through /proc/net/rxrpc_calls. This helps debugging stuck operations as we
then know what they are.
Signed-off-by: David Howells <[email protected]>
---
include/net/af_rxrpc.h | 1 +
net/rxrpc/af_rxrpc.c | 3 +++
net/rxrpc/ar-internal.h | 1 +
net/rxrpc/ar-proc.c | 7 ++++---
4 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/include/net/af_rxrpc.h b/include/net/af_rxrpc.h
index 00c2eaa..7e99733 100644
--- a/include/net/af_rxrpc.h
+++ b/include/net/af_rxrpc.h
@@ -38,6 +38,7 @@ extern void rxrpc_kernel_intercept_rx_messages(struct socket *,
extern struct rxrpc_call *rxrpc_kernel_begin_call(struct socket *,
struct sockaddr_rxrpc *,
struct key *,
+ u32,
unsigned long,
gfp_t);
extern int rxrpc_kernel_send_data(struct rxrpc_call *, struct msghdr *,
diff --git a/net/rxrpc/af_rxrpc.c b/net/rxrpc/af_rxrpc.c
index c58fa0d..621c1dd 100644
--- a/net/rxrpc/af_rxrpc.c
+++ b/net/rxrpc/af_rxrpc.c
@@ -251,6 +251,7 @@ static struct rxrpc_transport *rxrpc_name_to_transport(struct socket *sock,
* @sock: The socket on which to make the call
* @srx: The address of the peer to contact (defaults to socket setting)
* @key: The security context to use (defaults to socket setting)
+ * @operation_ID: The operation ID for this call (debugging only)
* @user_call_ID: The ID to use
*
* Allow a kernel service to begin a call on the nominated socket. This just
@@ -263,6 +264,7 @@ static struct rxrpc_transport *rxrpc_name_to_transport(struct socket *sock,
struct rxrpc_call *rxrpc_kernel_begin_call(struct socket *sock,
struct sockaddr_rxrpc *srx,
struct key *key,
+ u32 operation_ID,
unsigned long user_call_ID,
gfp_t gfp)
{
@@ -311,6 +313,7 @@ struct rxrpc_call *rxrpc_kernel_begin_call(struct socket *sock,
call = rxrpc_get_client_call(rx, trans, bundle, user_call_ID, true,
gfp);
rxrpc_put_bundle(trans, bundle);
+ call->op_id = operation_ID;
out:
rxrpc_put_transport(trans);
release_sock(&rx->sk);
diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h
index 58aaf89..f362e7e 100644
--- a/net/rxrpc/ar-internal.h
+++ b/net/rxrpc/ar-internal.h
@@ -367,6 +367,7 @@ struct rxrpc_call {
RXRPC_CALL_DEAD, /* - call is dead */
} state;
int debug_id; /* debug ID for printks */
+ u32 op_id; /* operation ID (for debugging only) */
u8 channel; /* connection channel occupied by this call */
/* transmission-phase ACK management */
diff --git a/net/rxrpc/ar-proc.c b/net/rxrpc/ar-proc.c
index 2e83ce3..521b826 100644
--- a/net/rxrpc/ar-proc.c
+++ b/net/rxrpc/ar-proc.c
@@ -53,8 +53,8 @@ static int rxrpc_call_seq_show(struct seq_file *seq, void *v)
if (v == &rxrpc_calls) {
seq_puts(seq,
"Proto Local Remote "
- " SvID ConnID CallID End Use State Abort "
- " UserID\n");
+ " SvID ConnID CallID OpID End Use State "
+ " Abort UserID\n");
return 0;
}
@@ -70,13 +70,14 @@ static int rxrpc_call_seq_show(struct seq_file *seq, void *v)
ntohs(trans->peer->srx.transport.sin.sin_port));
seq_printf(seq,
- "UDP %-22.22s %-22.22s %4x %08x %08x %s %3u"
+ "UDP %-22.22s %-22.22s %4x %08x %08x %08x %s %3u"
" %-8.8s %08x %lx\n",
lbuff,
rbuff,
ntohs(call->conn->service_id),
ntohl(call->conn->cid),
ntohl(call->call_id),
+ call->op_id,
call->conn->in_clientflag ? "Svc" : "Clt",
atomic_read(&call->usage),
rxrpc_call_states[call->state],
Add a function - cancel_rejected_write() - to excise a rejected write from the
pagecache. This function is related to the truncation family of routines. It
permits the pages modified by a network filesystem client (such as AFS) to be
excised and discarded from the pagecache if the attempt to write them back to
the server fails.
The dirty and writeback states of the afflicted pages are cancelled and the
pages themselves are detached for recycling. All PTEs referring to those
pages are removed.
Note that the locking is tricky as it's very easy to deadlock against
truncate() and other routines once the pages have been unlocked as part of the
writeback process. To this end, the PG_error flag is set, then the
PG_writeback flag is cleared, and only *then* can lock_page() be called.
Signed-off-by: David Howells <[email protected]>
---
include/linux/mm.h | 5 ++-
mm/truncate.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 86 insertions(+), 2 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 1692dd6..49863df 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1091,12 +1091,13 @@ extern int do_munmap(struct mm_struct *, unsigned long, size_t);
extern unsigned long do_brk(unsigned long, unsigned long);
-/* filemap.c */
-extern unsigned long page_unuse(struct page *);
+/* truncate.c */
extern void truncate_inode_pages(struct address_space *, loff_t);
extern void truncate_inode_pages_range(struct address_space *,
loff_t lstart, loff_t lend);
+extern void cancel_rejected_write(struct address_space *, pgoff_t, pgoff_t);
+/* filemap.c */
/* generic vm_area_ops exported for stackable file systems */
extern int filemap_fault(struct vm_area_struct *, struct vm_fault *);
diff --git a/mm/truncate.c b/mm/truncate.c
index 5555cb0..92a68f7 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -462,3 +462,86 @@ int invalidate_inode_pages2(struct address_space *mapping)
return invalidate_inode_pages2_range(mapping, 0, -1);
}
EXPORT_SYMBOL_GPL(invalidate_inode_pages2);
+
+/*
+ * Cancel that part of a rejected write that affects a particular page
+ */
+static void cancel_rejected_page(struct address_space *mapping,
+ struct page *page, pgoff_t *_next)
+{
+ if (!TestSetPageError(page)) {
+ /* can't lock the page until we've cleared PG_writeback lest we
+ * deadlock with truncate (amongst other things) */
+ end_page_writeback(page);
+ if (page->mapping == mapping) {
+ lock_page(page);
+ if (page->mapping == mapping) {
+ truncate_complete_page(mapping, page);
+ *_next = page->index + 1;
+ }
+ unlock_page(page);
+ }
+ } else if (PageWriteback(page) || PageDirty(page)) {
+ BUG();
+ }
+}
+
+/**
+ * cancel_rejected_write - Cancel a write on a contiguous set of pages
+ * @mapping: mapping affected
+ * @start: first page in set
+ * @end: last page in set
+ *
+ * Cancel a write of a contiguous set of pages when the writeback was rejected
+ * by the target medium or server.
+ *
+ * The pages in question are detached and discarded from the pagecache, and the
+ * writeback and dirty states are cleared prior to invalidation. The caller
+ * must make sure that all the pages in the range are present in the pagecache,
+ * and the caller must hold PG_writeback on each of them. NOTE! All the pages
+ * are locked and unlocked as part of this process, so the caller must take
+ * care to avoid deadlock.
+ *
+ * The PTEs pointing to those pages are also cleared, leading to the PTEs being
+ * reset when new pages are allocated and the contents reloaded.
+ */
+void cancel_rejected_write(struct address_space *mapping,
+ pgoff_t start, pgoff_t end)
+{
+ struct pagevec pvec;
+ pgoff_t n;
+ int i;
+
+ BUG_ON(mapping->nrpages < end - start + 1);
+
+ /* dispose of any PTEs pointing to the affected pages */
+ unmap_mapping_range(mapping,
+ (loff_t)start << PAGE_CACHE_SHIFT,
+ (loff_t)(end - start + 1) << PAGE_CACHE_SHIFT,
+ 0);
+
+ pagevec_init(&pvec, 0);
+ do {
+ cond_resched();
+ n = end - start + 1;
+ if (n > PAGEVEC_SIZE)
+ n = PAGEVEC_SIZE;
+ n = pagevec_lookup(&pvec, mapping, start, n);
+ for (i = 0; i < n; i++) {
+ struct page *page = pvec.pages[i];
+
+ if (page->index < start || page->index > end)
+ continue;
+ start++;
+ cancel_rejected_page(mapping, page, &start);
+ }
+ pagevec_release(&pvec);
+ } while (start - 1 < end);
+
+ /* dispose of any new PTEs pointing to the affected pages */
+ unmap_mapping_range(mapping,
+ (loff_t)start << PAGE_CACHE_SHIFT,
+ (loff_t)(end - start + 1) << PAGE_CACHE_SHIFT,
+ 0);
+}
+EXPORT_SYMBOL_GPL(cancel_rejected_write);
Improve the handling of the case of a server rejecting an attempt to write back
a cached write. AFS operates a write-back cache, so the following sequence of
events can theoretically occur:
CLIENT 1 CLIENT 2
======================= =======================
cat data >/the/file
(sits in pagecache)
fs setacl -dir /the/dir/of/the/file \
-acl system:administrators rlidka
(write permission removed for client 1)
sync
(writeback attempt fails)
The way AFS attempts to handle this is:
(1) The affected region will be excised and discarded on the basis that it
can't be written back, yet we don't want it lurking in the page cache
either. The contents of the affected region will be reread from the
server when called for again.
(2) The EOF size will be set to the current server-based file size - usually
that which it was before the affected write was made - assuming no
conflicting write has been appended, and assuming the affected write
extended the file.
This patch makes the following changes:
(1) Zero-length short reads don't produce EBADMSG now just because the OpenAFS
server puts a silly value as the size of the returned data. This prevents
excised pages beyond the revised EOF being reinstantiated with a surprise
PG_error.
(2) Writebacks can now be put into a 'rejected' state in which all further
attempts to write them back will result in excision of the affected pages
instead.
(3) Preparing a page for overwriting now reads the whole page instead of just
those parts of it that aren't to be covered by the copy to be made. This
handles the possibility that the copy might fail on EFAULT. Corollary to
this, PG_update can now be set by afs_prepare_page() on behalf of
afs_prepare_write() rather than setting it in afs_commit_write().
(4) In the case of a conflicting write, afs_prepare_write() will attempt to
flush the write to the server, and will then wait for PG_writeback to go
away - after unlocking the page. This helps prevent deadlock against the
writeback-rejection handler. AOP_TRUNCATED_PAGE is then returned to the
caller to signify that the page has been unlocked, and that it should be
revalidated.
(5) The writeback-rejection handler now calls cancel_rejected_write() added by
the previous patch to excise the affected pages rather than clearing the
PG_uptodate flag on all the pages.
Signed-off-by: David Howells <[email protected]>
---
fs/afs/fsclient.c | 4 +
fs/afs/internal.h | 1
fs/afs/write.c | 154 ++++++++++++++++++++++++++++-------------------------
3 files changed, 85 insertions(+), 74 deletions(-)
diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c
index 023b95b..04584c0 100644
--- a/fs/afs/fsclient.c
+++ b/fs/afs/fsclient.c
@@ -353,7 +353,9 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call,
call->count = ntohl(call->tmp);
_debug("DATA length: %u", call->count);
- if (call->count > PAGE_SIZE)
+ if ((s32) call->count < 0)
+ call->count = 0; /* access completely beyond EOF */
+ else if (call->count > PAGE_SIZE)
return -EBADMSG;
call->offset = 0;
call->unmarshall++;
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 6306438..e1bcce0 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -156,6 +156,7 @@ struct afs_writeback {
AFS_WBACK_PENDING, /* write pending */
AFS_WBACK_CONFLICTING, /* conflicting writes posted */
AFS_WBACK_WRITING, /* writing back */
+ AFS_WBACK_REJECTED, /* the writeback was rejected */
AFS_WBACK_COMPLETE /* the writeback record has been unlinked */
} state __attribute__((packed));
};
diff --git a/fs/afs/write.c b/fs/afs/write.c
index a03b92a..ac621e8 100644
--- a/fs/afs/write.c
+++ b/fs/afs/write.c
@@ -81,18 +81,16 @@ void afs_put_writeback(struct afs_writeback *wb)
}
/*
- * partly or wholly fill a page that's under preparation for writing
+ * fill a page that's under preparation for writing
*/
static int afs_fill_page(struct afs_vnode *vnode, struct key *key,
- unsigned start, unsigned len, struct page *page)
+ unsigned len, struct page *page)
{
int ret;
- _enter(",,%u,%u", start, len);
+ _enter(",,%u,", len);
- ASSERTCMP(start + len, <=, PAGE_SIZE);
-
- ret = afs_vnode_fetch_data(vnode, key, start, len, page);
+ ret = afs_vnode_fetch_data(vnode, key, 0, len, page);
if (ret < 0) {
if (ret == -ENOENT) {
_debug("got NOENT from server"
@@ -110,18 +108,15 @@ static int afs_fill_page(struct afs_vnode *vnode, struct key *key,
* prepare a page for being written to
*/
static int afs_prepare_page(struct afs_vnode *vnode, struct page *page,
- struct key *key, unsigned offset, unsigned to)
+ struct key *key)
{
- unsigned eof, tail, start, stop, len;
+ unsigned len;
loff_t i_size, pos;
void *p;
int ret;
_enter("");
- if (offset == 0 && to == PAGE_SIZE)
- return 0;
-
p = kmap_atomic(page, KM_USER0);
i_size = i_size_read(&vnode->vfs_inode);
@@ -129,10 +124,7 @@ static int afs_prepare_page(struct afs_vnode *vnode, struct page *page,
if (pos >= i_size) {
/* partial write, page beyond EOF */
_debug("beyond");
- if (offset > 0)
- memset(p, 0, offset);
- if (to < PAGE_SIZE)
- memset(p + to, 0, PAGE_SIZE - to);
+ memset(p, 0, PAGE_SIZE);
kunmap_atomic(p, KM_USER0);
return 0;
}
@@ -140,31 +132,20 @@ static int afs_prepare_page(struct afs_vnode *vnode, struct page *page,
if (i_size - pos >= PAGE_SIZE) {
/* partial write, page entirely before EOF */
_debug("before");
- tail = eof = PAGE_SIZE;
+ len = PAGE_SIZE;
} else {
/* partial write, page overlaps EOF */
- eof = i_size - pos;
- _debug("overlap %u", eof);
- tail = max(eof, to);
- if (tail < PAGE_SIZE)
- memset(p + tail, 0, PAGE_SIZE - tail);
- if (offset > eof)
- memset(p + eof, 0, PAGE_SIZE - eof);
+ len = i_size - pos;
+ _debug("overlap %u", len);
+ ASSERTRANGE(0, <, len, <, PAGE_SIZE);
+ memset(p + len, 0, PAGE_SIZE - len);
}
kunmap_atomic(p, KM_USER0);
- ret = 0;
- if (offset > 0 || eof > to) {
- /* need to fill one or two bits that aren't going to be written
- * (cover both fillers in one read if there are two) */
- start = (offset > 0) ? 0 : to;
- stop = (eof > to) ? eof : offset;
- len = stop - start;
- _debug("wr=%u-%u av=0-%u rd=%u@%u",
- offset, to, eof, start, len);
- ret = afs_fill_page(vnode, key, start, len, page);
- }
+ ret = afs_fill_page(vnode, key, len, page);
+ if (ret == 0)
+ SetPageUptodate(page);
_leave(" = %d", ret);
return ret;
@@ -187,6 +168,8 @@ int afs_prepare_write(struct file *file, struct page *page,
_enter("{%x:%u},{%lx},%u,%u",
vnode->fid.vid, vnode->fid.vnode, page->index, offset, to);
+ BUG_ON(PageError(page));
+
candidate = kzalloc(sizeof(*candidate), GFP_KERNEL);
if (!candidate)
return -ENOMEM;
@@ -200,7 +183,7 @@ int afs_prepare_write(struct file *file, struct page *page,
if (!PageUptodate(page)) {
_debug("not up to date");
- ret = afs_prepare_page(vnode, page, key, offset, to);
+ ret = afs_prepare_page(vnode, page, key);
if (ret < 0) {
kfree(candidate);
_leave(" = %d [prep]", ret);
@@ -269,21 +252,41 @@ flush_conflicting_wb:
_debug("flush conflict");
if (wb->state == AFS_WBACK_PENDING)
wb->state = AFS_WBACK_CONFLICTING;
+ wb->usage++;
spin_unlock(&vnode->writeback_lock);
- if (PageDirty(page)) {
+ if (!PageDirty(page) && !PageWriteback(page)) {
+ /* no change outstanding - just reuse the page */
+ _debug("reuse");
+ afs_put_writeback(wb);
+ set_page_private(page, 0);
+ ClearPagePrivate(page);
+ goto try_again;
+ }
+
+ kfree(candidate);
+
+ /* if we're busy writing back a conflicting write, then unlock the page
+ * and wait for the writeback to complete - this lets the process doing
+ * the write-out handle rejection without deadlock */
+ if (PageWriteback(page)) {
+ _debug("wait wb");
+ unlock_page(page);
+ } else {
+ /* there's a conflicting modification we have to write back and
+ * wait for before letting the next one proceed */
+ _debug("dirty");
ret = afs_write_back_from_locked_page(wb, page);
if (ret < 0) {
- afs_put_writeback(candidate);
+ afs_put_writeback(wb);
_leave(" = %d", ret);
return ret;
}
}
- /* the page holds a ref on the writeback record */
afs_put_writeback(wb);
- set_page_private(page, 0);
- ClearPagePrivate(page);
- goto try_again;
+ wait_on_page_writeback(page);
+ _leave(" = A_T_P");
+ return AOP_TRUNCATED_PAGE;
}
/*
@@ -310,7 +313,6 @@ int afs_commit_write(struct file *file, struct page *page,
spin_unlock(&vnode->writeback_lock);
}
- SetPageUptodate(page);
set_page_dirty(page);
if (PageDirty(page))
_debug("dirtied");
@@ -319,38 +321,35 @@ int afs_commit_write(struct file *file, struct page *page,
}
/*
- * kill all the pages in the given range
+ * note the failure of a write, either due to an error or to a permission
+ * failure
+ * - all the pages in the affected range must have PG_writeback set
+ * - the caller must be responsible for the pages: no-one else should be trying
+ * to note rejection
*/
-static void afs_kill_pages(struct afs_vnode *vnode, bool error,
- pgoff_t first, pgoff_t last)
+static void afs_write_rejected(struct afs_writeback *wb, bool error,
+ pgoff_t first, pgoff_t last)
{
- struct pagevec pv;
- unsigned count, loop;
+ struct afs_vnode *vnode = wb->vnode;
+ loff_t i_size;
_enter("{%x:%u},%lx-%lx",
vnode->fid.vid, vnode->fid.vnode, first, last);
- pagevec_init(&pv, 0);
-
- do {
- _debug("kill %lx-%lx", first, last);
-
- count = last - first + 1;
- if (count > PAGEVEC_SIZE)
- count = PAGEVEC_SIZE;
- pv.nr = find_get_pages_contig(vnode->vfs_inode.i_mapping,
- first, count, pv.pages);
- ASSERTCMP(pv.nr, ==, count);
-
- for (loop = 0; loop < count; loop++) {
- ClearPageUptodate(pv.pages[loop]);
- if (error)
- SetPageError(pv.pages[loop]);
- end_page_writeback(pv.pages[loop]);
- }
+ spin_lock(&vnode->writeback_lock);
+ wb->state = AFS_WBACK_REJECTED;
+
+ /* wind back the file size if this write extended the file, and wasn't
+ * followed by a conflicting write */
+ i_size = ((loff_t) wb->last) << PAGE_SHIFT;
+ i_size += wb->to_last;
+ if (i_size_read(&vnode->vfs_inode) == i_size) {
+ _debug("shorten");
+ i_size_write(&vnode->vfs_inode, vnode->status.size);
+ }
- __pagevec_release(&pv);
- } while (first < last);
+ spin_unlock(&vnode->writeback_lock);
+ cancel_rejected_write(vnode->vfs_inode.i_mapping, first, last);
_leave("");
}
@@ -358,6 +357,7 @@ static void afs_kill_pages(struct afs_vnode *vnode, bool error,
/*
* synchronously write back the locked page and any subsequent non-locked dirty
* pages also covered by the same writeback record
+ * - all pages written will be unlocked prior to returning
*/
static int afs_write_back_from_locked_page(struct afs_writeback *wb,
struct page *primary_page)
@@ -407,6 +407,7 @@ static int afs_write_back_from_locked_page(struct afs_writeback *wb,
if (TestSetPageLocked(page))
break;
if (!PageDirty(page) ||
+ PageWriteback(page) ||
page_private(page) != (unsigned long) wb) {
unlock_page(page);
break;
@@ -430,14 +431,22 @@ static int afs_write_back_from_locked_page(struct afs_writeback *wb,
no_more:
/* we now have a contiguous set of dirty pages, each with writeback set
- * and the dirty mark cleared; the first page is locked and must remain
- * so, all the rest are unlocked */
+ * and the dirty mark cleared; all the pages barring the first are now
+ * unlocked */
first = primary_page->index;
last = first + count - 1;
+ unlock_page(primary_page);
offset = (first == wb->first) ? wb->offset_first : 0;
to = (last == wb->last) ? wb->to_last : PAGE_SIZE;
+ if (wb->state == AFS_WBACK_REJECTED) {
+ cancel_rejected_write(wb->vnode->vfs_inode.i_mapping,
+ first, last);
+ ret = 0;
+ goto out;
+ }
+
_debug("write back %lx[%u..] to %lx[..%u]", first, offset, last, to);
ret = afs_vnode_store_data(wb, first, last, offset, to);
@@ -455,7 +464,7 @@ no_more:
case -ENOENT:
case -ENOMEDIUM:
case -ENXIO:
- afs_kill_pages(wb->vnode, true, first, last);
+ afs_write_rejected(wb, true, first, last);
set_bit(AS_EIO, &wb->vnode->vfs_inode.i_mapping->flags);
break;
case -EACCES:
@@ -464,7 +473,7 @@ no_more:
case -EKEYEXPIRED:
case -EKEYREJECTED:
case -EKEYREVOKED:
- afs_kill_pages(wb->vnode, false, first, last);
+ afs_write_rejected(wb, false, first, last);
break;
default:
break;
@@ -473,6 +482,7 @@ no_more:
ret = count;
}
+out:
_leave(" = %d", ret);
return ret;
}
@@ -493,7 +503,6 @@ int afs_writepage(struct page *page, struct writeback_control *wbc)
ASSERT(wb != NULL);
ret = afs_write_back_from_locked_page(wb, page);
- unlock_page(page);
if (ret < 0) {
_leave(" = %d", ret);
return 0;
@@ -565,7 +574,6 @@ int afs_writepages_region(struct address_space *mapping,
spin_unlock(&wb->vnode->writeback_lock);
ret = afs_write_back_from_locked_page(wb, page);
- unlock_page(page);
page_cache_release(page);
if (ret < 0) {
_leave(" = %d", ret);
The attached patch makes the kAFS filesystem in fs/afs/ use FS-Cache, and
through it any attached caches. The kAFS filesystem will use caching
automatically if it's available.
Signed-Off-By: David Howells <[email protected]>
---
fs/Kconfig | 8 +
fs/afs/Makefile | 3
fs/afs/cache.c | 505 ++++++++++++++++++++++++++++++++++------------------
fs/afs/cache.h | 15 --
fs/afs/cell.c | 16 +-
fs/afs/file.c | 212 +++++++++++++---------
fs/afs/fsclient.c | 32 ++-
fs/afs/inode.c | 25 +--
fs/afs/internal.h | 53 ++---
fs/afs/main.c | 27 +--
fs/afs/mntpt.c | 4
fs/afs/rxrpc.c | 1
fs/afs/vlclient.c | 2
fs/afs/vlocation.c | 23 +-
fs/afs/volume.c | 14 -
fs/afs/write.c | 6 -
16 files changed, 563 insertions(+), 383 deletions(-)
diff --git a/fs/Kconfig b/fs/Kconfig
index ebc7341..158a8d8 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -2059,6 +2059,14 @@ config AFS_DEBUG
If unsure, say N.
+config AFS_FSCACHE
+ bool "Provide AFS client caching support (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ depends on AFS_FS=m && FSCACHE || AFS_FS=y && FSCACHE=y
+ help
+ Say Y here if you want AFS data to be cached locally on disk through
+ the generic filesystem cache manager
+
config 9P_FS
tristate "Plan 9 Resource Sharing Support (9P2000) (Experimental)"
depends on INET && NET_9P && EXPERIMENTAL
diff --git a/fs/afs/Makefile b/fs/afs/Makefile
index a666710..4f64b95 100644
--- a/fs/afs/Makefile
+++ b/fs/afs/Makefile
@@ -2,7 +2,10 @@
# Makefile for Red Hat Linux AFS client.
#
+afs-cache-$(CONFIG_AFS_FSCACHE) := cache.o
+
kafs-objs := \
+ $(afs-cache-y) \
callback.o \
cell.o \
cmservice.o \
diff --git a/fs/afs/cache.c b/fs/afs/cache.c
index de0d7de..a5d6a70 100644
--- a/fs/afs/cache.c
+++ b/fs/afs/cache.c
@@ -9,248 +9,399 @@
* 2 of the License, or (at your option) any later version.
*/
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_cell_cache_match(void *target,
- const void *entry);
-static void afs_cell_cache_update(void *source, void *entry);
-
-struct cachefs_index_def afs_cache_cell_index_def = {
- .name = "cell_ix",
- .data_size = sizeof(struct afs_cache_cell),
- .keys[0] = { CACHEFS_INDEX_KEYS_ASCIIZ, 64 },
- .match = afs_cell_cache_match,
- .update = afs_cell_cache_update,
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include "internal.h"
+
+static uint16_t afs_cell_cache_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t buflen);
+static uint16_t afs_cell_cache_get_aux(const void *cookie_netfs_data,
+ void *buffer, uint16_t buflen);
+static fscache_checkaux_t afs_cell_cache_check_aux(void *cookie_netfs_data,
+ const void *buffer,
+ uint16_t buflen);
+
+static uint16_t afs_vlocation_cache_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t buflen);
+static uint16_t afs_vlocation_cache_get_aux(const void *cookie_netfs_data,
+ void *buffer, uint16_t buflen);
+static fscache_checkaux_t afs_vlocation_cache_check_aux(void *cookie_netfs_data,
+ const void *buffer,
+ uint16_t buflen);
+
+static uint16_t afs_volume_cache_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t buflen);
+
+static uint16_t afs_vnode_cache_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t buflen);
+static void afs_vnode_cache_get_attr(const void *cookie_netfs_data,
+ uint64_t *size);
+static uint16_t afs_vnode_cache_get_aux(const void *cookie_netfs_data,
+ void *buffer, uint16_t buflen);
+static fscache_checkaux_t afs_vnode_cache_check_aux(void *cookie_netfs_data,
+ const void *buffer,
+ uint16_t buflen);
+static void afs_vnode_cache_now_uncached(void *cookie_netfs_data);
+
+static struct fscache_netfs_operations afs_cache_ops = {
+};
+
+struct fscache_netfs afs_cache_netfs = {
+ .name = "afs",
+ .version = 0,
+ .ops = &afs_cache_ops,
+};
+
+struct fscache_cookie_def afs_cell_cache_index_def = {
+ .name = "AFS.cell",
+ .type = FSCACHE_COOKIE_TYPE_INDEX,
+ .get_key = afs_cell_cache_get_key,
+ .get_aux = afs_cell_cache_get_aux,
+ .check_aux = afs_cell_cache_check_aux,
+};
+
+struct fscache_cookie_def afs_vlocation_cache_index_def = {
+ .name = "AFS.vldb",
+ .type = FSCACHE_COOKIE_TYPE_INDEX,
+ .get_key = afs_vlocation_cache_get_key,
+ .get_aux = afs_vlocation_cache_get_aux,
+ .check_aux = afs_vlocation_cache_check_aux,
+};
+
+struct fscache_cookie_def afs_volume_cache_index_def = {
+ .name = "AFS.volume",
+ .type = FSCACHE_COOKIE_TYPE_INDEX,
+ .get_key = afs_volume_cache_get_key,
+};
+
+struct fscache_cookie_def afs_vnode_cache_index_def = {
+ .name = "AFS.vnode",
+ .type = FSCACHE_COOKIE_TYPE_DATAFILE,
+ .get_key = afs_vnode_cache_get_key,
+ .get_attr = afs_vnode_cache_get_attr,
+ .get_aux = afs_vnode_cache_get_aux,
+ .check_aux = afs_vnode_cache_check_aux,
+ .now_uncached = afs_vnode_cache_now_uncached,
};
-#endif
/*
- * match a cell record obtained from the cache
+ * set the key for the index entry
*/
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_cell_cache_match(void *target,
- const void *entry)
+static uint16_t afs_cell_cache_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
{
- const struct afs_cache_cell *ccell = entry;
- struct afs_cell *cell = target;
+ const struct afs_cell *cell = cookie_netfs_data;
+ uint16_t klen;
- _enter("{%s},{%s}", ccell->name, cell->name);
+ _enter("%p,%p,%u", cell, buffer, bufmax);
- if (strncmp(ccell->name, cell->name, sizeof(ccell->name)) == 0) {
- _leave(" = SUCCESS");
- return CACHEFS_MATCH_SUCCESS;
- }
+ klen = strlen(cell->name);
+ if (klen > bufmax)
+ return 0;
- _leave(" = FAILED");
- return CACHEFS_MATCH_FAILED;
+ memcpy(buffer, cell->name, klen);
+ return klen;
}
-#endif
/*
- * update a cell record in the cache
+ * provide new auxilliary cache data
*/
-#ifdef AFS_CACHING_SUPPORT
-static void afs_cell_cache_update(void *source, void *entry)
+static uint16_t afs_cell_cache_get_aux(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
{
- struct afs_cache_cell *ccell = entry;
- struct afs_cell *cell = source;
+ const struct afs_cell *cell = cookie_netfs_data;
+ uint16_t dlen;
- _enter("%p,%p", source, entry);
+ _enter("%p,%p,%u", cell, buffer, bufmax);
- strncpy(ccell->name, cell->name, sizeof(ccell->name));
+ dlen = cell->vl_naddrs * sizeof(cell->vl_addrs[0]);
+ dlen = min(dlen, bufmax);
+ dlen &= ~(sizeof(cell->vl_addrs[0]) - 1);
- memcpy(ccell->vl_servers,
- cell->vl_addrs,
- min(sizeof(ccell->vl_servers), sizeof(cell->vl_addrs)));
+ memcpy(buffer, cell->vl_addrs, dlen);
+ return dlen;
+}
+/*
+ * check that the auxilliary data indicates that the entry is still valid
+ */
+static fscache_checkaux_t afs_cell_cache_check_aux(void *cookie_netfs_data,
+ const void *buffer,
+ uint16_t buflen)
+{
+ _leave(" = OKAY");
+ return FSCACHE_CHECKAUX_OKAY;
}
-#endif
-
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_vlocation_cache_match(void *target,
- const void *entry);
-static void afs_vlocation_cache_update(void *source, void *entry);
-
-struct cachefs_index_def afs_vlocation_cache_index_def = {
- .name = "vldb",
- .data_size = sizeof(struct afs_cache_vlocation),
- .keys[0] = { CACHEFS_INDEX_KEYS_ASCIIZ, 64 },
- .match = afs_vlocation_cache_match,
- .update = afs_vlocation_cache_update,
-};
-#endif
+/*****************************************************************************/
/*
- * match a VLDB record stored in the cache
- * - may also load target from entry
+ * set the key for the index entry
*/
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_vlocation_cache_match(void *target,
- const void *entry)
+static uint16_t afs_vlocation_cache_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
{
- const struct afs_cache_vlocation *vldb = entry;
- struct afs_vlocation *vlocation = target;
+ const struct afs_vlocation *vlocation = cookie_netfs_data;
+ uint16_t klen;
- _enter("{%s},{%s}", vlocation->vldb.name, vldb->name);
+ _enter("{%s},%p,%u", vlocation->vldb.name, buffer, bufmax);
- if (strncmp(vlocation->vldb.name, vldb->name, sizeof(vldb->name)) == 0
- ) {
- if (!vlocation->valid ||
- vlocation->vldb.rtime == vldb->rtime
+ klen = strnlen(vlocation->vldb.name, sizeof(vlocation->vldb.name));
+ if (klen > bufmax)
+ return 0;
+
+ memcpy(buffer, vlocation->vldb.name, klen);
+
+ _leave(" = %u", klen);
+ return klen;
+}
+
+/*
+ * provide new auxilliary cache data
+ */
+static uint16_t afs_vlocation_cache_get_aux(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
+{
+ const struct afs_vlocation *vlocation = cookie_netfs_data;
+ uint16_t dlen;
+
+ _enter("{%s},%p,%u", vlocation->vldb.name, buffer, bufmax);
+
+ dlen = sizeof(struct afs_cache_vlocation);
+ dlen -= offsetof(struct afs_cache_vlocation, nservers);
+ if (dlen > bufmax)
+ return 0;
+
+ memcpy(buffer, (uint8_t *)&vlocation->vldb.nservers, dlen);
+
+ _leave(" = %u", dlen);
+ return dlen;
+}
+
+/*
+ * check that the auxilliary data indicates that the entry is still valid
+ */
+static fscache_checkaux_t afs_vlocation_cache_check_aux(void *cookie_netfs_data,
+ const void *buffer,
+ uint16_t buflen)
+{
+ const struct afs_cache_vlocation *cvldb;
+ struct afs_vlocation *vlocation = cookie_netfs_data;
+ uint16_t dlen;
+
+ _enter("{%s},%p,%u", vlocation->vldb.name, buffer, buflen);
+
+ /* check the size of the data is what we're expecting */
+ dlen = sizeof(struct afs_cache_vlocation);
+ dlen -= offsetof(struct afs_cache_vlocation, nservers);
+ if (dlen != buflen)
+ return FSCACHE_CHECKAUX_OBSOLETE;
+
+ cvldb = container_of(buffer, struct afs_cache_vlocation, nservers);
+
+ /* if what's on disk is more valid than what's in memory, then use the
+ * VL record from the cache */
+ if (!vlocation->valid || vlocation->vldb.rtime == cvldb->rtime) {
+ memcpy((uint8_t *)&vlocation->vldb.nservers, buffer, dlen);
+ vlocation->valid = 1;
+ _leave(" = SUCCESS [c->m]");
+ return FSCACHE_CHECKAUX_OKAY;
+ }
+
+ /* need to update the cache if the cached info differs */
+ if (memcmp(&vlocation->vldb, buffer, dlen) != 0) {
+ /* delete if the volume IDs for this name differ */
+ if (memcmp(&vlocation->vldb.vid, &cvldb->vid,
+ sizeof(cvldb->vid)) != 0
) {
- vlocation->vldb = *vldb;
- vlocation->valid = 1;
- _leave(" = SUCCESS [c->m]");
- return CACHEFS_MATCH_SUCCESS;
- } else if (memcmp(&vlocation->vldb, vldb, sizeof(*vldb)) != 0) {
- /* delete if VIDs for this name differ */
- if (memcmp(&vlocation->vldb.vid,
- &vldb->vid,
- sizeof(vldb->vid)) != 0) {
- _leave(" = DELETE");
- return CACHEFS_MATCH_SUCCESS_DELETE;
- }
-
- _leave(" = UPDATE");
- return CACHEFS_MATCH_SUCCESS_UPDATE;
- } else {
- _leave(" = SUCCESS");
- return CACHEFS_MATCH_SUCCESS;
+ _leave(" = OBSOLETE");
+ return FSCACHE_CHECKAUX_OBSOLETE;
}
+
+ _leave(" = UPDATE");
+ return FSCACHE_CHECKAUX_NEEDS_UPDATE;
}
- _leave(" = FAILED");
- return CACHEFS_MATCH_FAILED;
+ _leave(" = OKAY");
+ return FSCACHE_CHECKAUX_OKAY;
}
-#endif
+/*****************************************************************************/
/*
- * update a VLDB record stored in the cache
+ * set the key for the volume index entry
*/
-#ifdef AFS_CACHING_SUPPORT
-static void afs_vlocation_cache_update(void *source, void *entry)
+static uint16_t afs_volume_cache_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
{
- struct afs_cache_vlocation *vldb = entry;
- struct afs_vlocation *vlocation = source;
+ const struct afs_volume *volume = cookie_netfs_data;
+ uint16_t klen;
+
+ _enter("{%u},%p,%u", volume->type, buffer, bufmax);
- _enter("");
+ klen = sizeof(volume->type);
+ if (klen > bufmax)
+ return 0;
+
+ memcpy(buffer, &volume->type, sizeof(volume->type));
+
+ _leave(" = %u", klen);
+ return klen;
- *vldb = vlocation->vldb;
}
-#endif
-
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_volume_cache_match(void *target,
- const void *entry);
-static void afs_volume_cache_update(void *source, void *entry);
-
-struct cachefs_index_def afs_volume_cache_index_def = {
- .name = "volume",
- .data_size = sizeof(struct afs_cache_vhash),
- .keys[0] = { CACHEFS_INDEX_KEYS_BIN, 1 },
- .keys[1] = { CACHEFS_INDEX_KEYS_BIN, 1 },
- .match = afs_volume_cache_match,
- .update = afs_volume_cache_update,
-};
-#endif
+/*****************************************************************************/
/*
- * match a volume hash record stored in the cache
+ * set the key for the index entry
*/
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_volume_cache_match(void *target,
- const void *entry)
+static uint16_t afs_vnode_cache_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
{
- const struct afs_cache_vhash *vhash = entry;
- struct afs_volume *volume = target;
+ const struct afs_vnode *vnode = cookie_netfs_data;
+ uint16_t klen;
- _enter("{%u},{%u}", volume->type, vhash->vtype);
+ _enter("{%x,%x,%llx},%p,%u",
+ vnode->fid.vnode, vnode->fid.unique, vnode->status.data_version,
+ buffer, bufmax);
- if (volume->type == vhash->vtype) {
- _leave(" = SUCCESS");
- return CACHEFS_MATCH_SUCCESS;
- }
+ klen = sizeof(vnode->fid.vnode);
+ if (klen > bufmax)
+ return 0;
- _leave(" = FAILED");
- return CACHEFS_MATCH_FAILED;
+ memcpy(buffer, &vnode->fid.vnode, sizeof(vnode->fid.vnode));
+
+ _leave(" = %u", klen);
+ return klen;
}
-#endif
/*
- * update a volume hash record stored in the cache
+ * provide updated file attributes
*/
-#ifdef AFS_CACHING_SUPPORT
-static void afs_volume_cache_update(void *source, void *entry)
+static void afs_vnode_cache_get_attr(const void *cookie_netfs_data,
+ uint64_t *size)
{
- struct afs_cache_vhash *vhash = entry;
- struct afs_volume *volume = source;
+ const struct afs_vnode *vnode = cookie_netfs_data;
- _enter("");
+ _enter("{%x,%x,%llx},",
+ vnode->fid.vnode, vnode->fid.unique,
+ vnode->status.data_version);
- vhash->vtype = volume->type;
+ *size = vnode->status.size;
}
-#endif
-
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_vnode_cache_match(void *target,
- const void *entry);
-static void afs_vnode_cache_update(void *source, void *entry);
-
-struct cachefs_index_def afs_vnode_cache_index_def = {
- .name = "vnode",
- .data_size = sizeof(struct afs_cache_vnode),
- .keys[0] = { CACHEFS_INDEX_KEYS_BIN, 4 },
- .match = afs_vnode_cache_match,
- .update = afs_vnode_cache_update,
-};
-#endif
/*
- * match a vnode record stored in the cache
+ * provide new auxilliary cache data
*/
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_vnode_cache_match(void *target,
- const void *entry)
+static uint16_t afs_vnode_cache_get_aux(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
{
- const struct afs_cache_vnode *cvnode = entry;
- struct afs_vnode *vnode = target;
-
- _enter("{%x,%x,%Lx},{%x,%x,%Lx}",
- vnode->fid.vnode,
- vnode->fid.unique,
- vnode->status.version,
- cvnode->vnode_id,
- cvnode->vnode_unique,
- cvnode->data_version);
-
- if (vnode->fid.vnode != cvnode->vnode_id) {
- _leave(" = FAILED");
- return CACHEFS_MATCH_FAILED;
+ const struct afs_vnode *vnode = cookie_netfs_data;
+ uint16_t dlen;
+
+ _enter("{%x,%x,%Lx},%p,%u",
+ vnode->fid.vnode, vnode->fid.unique, vnode->status.data_version,
+ buffer, bufmax);
+
+ dlen = sizeof(vnode->fid.unique) + sizeof(vnode->status.data_version);
+ if (dlen > bufmax)
+ return 0;
+
+ memcpy(buffer, &vnode->fid.unique, sizeof(vnode->fid.unique));
+ buffer += sizeof(vnode->fid.unique);
+ memcpy(buffer, &vnode->status.data_version,
+ sizeof(vnode->status.data_version));
+
+ _leave(" = %u", dlen);
+ return dlen;
+}
+
+/*
+ * check that the auxilliary data indicates that the entry is still valid
+ */
+static fscache_checkaux_t afs_vnode_cache_check_aux(void *cookie_netfs_data,
+ const void *buffer,
+ uint16_t buflen)
+{
+ struct afs_vnode *vnode = cookie_netfs_data;
+ uint16_t dlen;
+
+ _enter("{%x,%x,%llx},%p,%u",
+ vnode->fid.vnode, vnode->fid.unique, vnode->status.data_version,
+ buffer, buflen);
+
+ /* check the size of the data is what we're expecting */
+ dlen = sizeof(vnode->fid.unique) + sizeof(vnode->status.data_version);
+ if (dlen != buflen) {
+ _leave(" = OBSOLETE [len %hx != %hx]", dlen, buflen);
+ return FSCACHE_CHECKAUX_OBSOLETE;
}
- if (vnode->fid.unique != cvnode->vnode_unique ||
- vnode->status.version != cvnode->data_version) {
- _leave(" = DELETE");
- return CACHEFS_MATCH_SUCCESS_DELETE;
+ if (memcmp(buffer,
+ &vnode->fid.unique,
+ sizeof(vnode->fid.unique)
+ ) != 0) {
+ unsigned unique;
+
+ memcpy(&unique, buffer, sizeof(unique));
+
+ _leave(" = OBSOLETE [uniq %x != %x]",
+ unique, vnode->fid.unique);
+ return FSCACHE_CHECKAUX_OBSOLETE;
+ }
+
+ if (memcmp(buffer + sizeof(vnode->fid.unique),
+ &vnode->status.data_version,
+ sizeof(vnode->status.data_version)
+ ) != 0) {
+ afs_dataversion_t version;
+
+ memcpy(&version, buffer + sizeof(vnode->fid.unique),
+ sizeof(version));
+
+ _leave(" = OBSOLETE [vers %llx != %llx]",
+ version, vnode->status.data_version);
+ return FSCACHE_CHECKAUX_OBSOLETE;
}
_leave(" = SUCCESS");
- return CACHEFS_MATCH_SUCCESS;
+ return FSCACHE_CHECKAUX_OKAY;
}
-#endif
/*
- * update a vnode record stored in the cache
+ * indication the cookie is no longer uncached
+ * - this function is called when the backing store currently caching a cookie
+ * is removed
+ * - the netfs should use this to clean up any markers indicating cached pages
+ * - this is mandatory for any object that may have data
*/
-#ifdef AFS_CACHING_SUPPORT
-static void afs_vnode_cache_update(void *source, void *entry)
+static void afs_vnode_cache_now_uncached(void *cookie_netfs_data)
{
- struct afs_cache_vnode *cvnode = entry;
- struct afs_vnode *vnode = source;
+ struct afs_vnode *vnode = cookie_netfs_data;
+ struct pagevec pvec;
+ pgoff_t first;
+ int loop, nr_pages;
+
+ _enter("{%x,%x,%Lx}",
+ vnode->fid.vnode, vnode->fid.unique, vnode->status.data_version);
+
+ pagevec_init(&pvec, 0);
+ first = 0;
+
+ for (;;) {
+ /* grab a bunch of pages to clean */
+ nr_pages = pagevec_lookup(&pvec, vnode->vfs_inode.i_mapping,
+ first,
+ PAGEVEC_SIZE - pagevec_count(&pvec));
+ if (!nr_pages)
+ break;
- _enter("");
+ 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();
+ }
- cvnode->vnode_id = vnode->fid.vnode;
- cvnode->vnode_unique = vnode->fid.unique;
- cvnode->data_version = vnode->status.version;
+ _leave("");
}
-#endif
diff --git a/fs/afs/cache.h b/fs/afs/cache.h
index 36a3642..b985052 100644
--- a/fs/afs/cache.h
+++ b/fs/afs/cache.h
@@ -1,6 +1,6 @@
/* AFS local cache management interface
*
- * Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
* Written by David Howells ([email protected])
*
* This program is free software; you can redistribute it and/or
@@ -9,15 +9,4 @@
* 2 of the License, or (at your option) any later version.
*/
-#ifndef AFS_CACHE_H
-#define AFS_CACHE_H
-
-#undef AFS_CACHING_SUPPORT
-
-#include <linux/mm.h>
-#ifdef AFS_CACHING_SUPPORT
-#include <linux/cachefs.h>
-#endif
-#include "types.h"
-
-#endif /* AFS_CACHE_H */
+#include <linux/fscache.h>
diff --git a/fs/afs/cell.c b/fs/afs/cell.c
index 175a567..950df56 100644
--- a/fs/afs/cell.c
+++ b/fs/afs/cell.c
@@ -145,12 +145,11 @@ struct afs_cell *afs_cell_create(const char *name, char *vllist)
if (ret < 0)
goto error;
-#ifdef AFS_CACHING_SUPPORT
- /* put it up for caching */
- cachefs_acquire_cookie(afs_cache_netfs.primary_index,
- &afs_vlocation_cache_index_def,
- cell,
- &cell->cache);
+#ifdef CONFIG_AFS_FSCACHE
+ /* put it up for caching (this never returns an error) */
+ cell->cache = fscache_acquire_cookie(afs_cache_netfs.primary_index,
+ &afs_cell_cache_index_def,
+ cell);
#endif
/* add to the cell lists */
@@ -353,10 +352,7 @@ static void afs_cell_destroy(struct afs_cell *cell)
list_del_init(&cell->proc_link);
up_write(&afs_proc_cells_sem);
-#ifdef AFS_CACHING_SUPPORT
- cachefs_relinquish_cookie(cell->cache, 0);
-#endif
-
+ fscache_relinquish_cookie(cell->cache, 0);
key_put(cell->anonymous_key);
kfree(cell);
diff --git a/fs/afs/file.c b/fs/afs/file.c
index 1323df4..276ed86 100644
--- a/fs/afs/file.c
+++ b/fs/afs/file.c
@@ -24,6 +24,9 @@ static int afs_releasepage(struct page *page, gfp_t gfp_flags);
static int afs_launder_page(struct page *page);
static int afs_mmap(struct file *file, struct vm_area_struct *vma);
+static int afs_readpages(struct file *filp, struct address_space *mapping,
+ struct list_head *pages, unsigned nr_pages);
+
const struct file_operations afs_file_operations = {
.open = afs_open,
.release = afs_release,
@@ -47,6 +50,7 @@ const struct inode_operations afs_file_inode_operations = {
const struct address_space_operations afs_fs_aops = {
.readpage = afs_readpage,
+ .readpages = afs_readpages,
.set_page_dirty = afs_set_page_dirty,
.launder_page = afs_launder_page,
.releasepage = afs_releasepage,
@@ -107,37 +111,18 @@ int afs_release(struct inode *inode, struct file *file)
/*
* deal with notification that a page was read from the cache
*/
-#ifdef AFS_CACHING_SUPPORT
-static void afs_readpage_read_complete(void *cookie_data,
- struct page *page,
- void *data,
- int error)
+static void afs_file_readpage_read_complete(struct page *page,
+ void *data,
+ int error)
{
- _enter("%p,%p,%p,%d", cookie_data, page, data, error);
+ _enter("%p,%p,%d", page, data, error);
- if (error)
- SetPageError(page);
- else
+ /* if the read completes with an error, we just unlock the page and let
+ * the VM reissue the readpage */
+ if (!error)
SetPageUptodate(page);
unlock_page(page);
-
-}
-#endif
-
-/*
- * deal with notification that a page was written to the cache
- */
-#ifdef AFS_CACHING_SUPPORT
-static void afs_readpage_write_complete(void *cookie_data,
- struct page *page,
- void *data,
- int error)
-{
- _enter("%p,%p,%p,%d", cookie_data, page, data, error);
-
- unlock_page(page);
}
-#endif
/*
* AFS read page from file, directory or symlink
@@ -167,30 +152,27 @@ static int afs_readpage(struct file *file, struct page *page)
if (test_bit(AFS_VNODE_DELETED, &vnode->flags))
goto error;
-#ifdef AFS_CACHING_SUPPORT
/* is it cached? */
- ret = cachefs_read_or_alloc_page(vnode->cache,
+ ret = fscache_read_or_alloc_page(vnode->cache,
page,
afs_file_readpage_read_complete,
NULL,
GFP_KERNEL);
-#else
- ret = -ENOBUFS;
-#endif
-
switch (ret) {
- /* read BIO submitted and wb-journal entry found */
- case 1:
- BUG(); // TODO - handle wb-journal match
-
/* read BIO submitted (page in cache) */
case 0:
break;
- /* no page available in cache */
- case -ENOBUFS:
+ /* page not yet cached */
case -ENODATA:
+ _debug("cache said ENODATA");
+ goto go_on;
+
+ /* page will not be cached */
+ case -ENOBUFS:
+ _debug("cache said ENOBUFS");
default:
+ go_on:
offset = page->index << PAGE_CACHE_SHIFT;
len = min_t(size_t, i_size_read(inode) - offset, PAGE_SIZE);
@@ -204,27 +186,21 @@ static int afs_readpage(struct file *file, struct page *page)
set_bit(AFS_VNODE_DELETED, &vnode->flags);
ret = -ESTALE;
}
-#ifdef AFS_CACHING_SUPPORT
- cachefs_uncache_page(vnode->cache, page);
-#endif
+
+ fscache_uncache_page(vnode->cache, page);
+ BUG_ON(PageFsCache(page));
goto error;
}
SetPageUptodate(page);
-#ifdef AFS_CACHING_SUPPORT
- if (cachefs_write_page(vnode->cache,
- page,
- afs_file_readpage_write_complete,
- NULL,
- GFP_KERNEL) != 0
- ) {
- cachefs_uncache_page(vnode->cache, page);
- unlock_page(page);
+ /* send the page to the cache */
+ if (PageFsCache(page) &&
+ fscache_write_page(vnode->cache, page, GFP_KERNEL) != 0) {
+ fscache_uncache_page(vnode->cache, page);
+ BUG_ON(PageFsCache(page));
}
-#else
unlock_page(page);
-#endif
}
_leave(" = 0");
@@ -238,34 +214,55 @@ error:
}
/*
- * invalidate part or all of a page
+ * read a set of pages
*/
-static void afs_invalidatepage(struct page *page, unsigned long offset)
+static int afs_readpages(struct file *file, struct address_space *mapping,
+ struct list_head *pages, unsigned nr_pages)
{
- int ret = 1;
+ struct afs_vnode *vnode;
+ int ret = 0;
- _enter("{%lu},%lu", page->index, offset);
+ _enter(",{%lu},,%d", mapping->host->i_ino, nr_pages);
- BUG_ON(!PageLocked(page));
+ vnode = AFS_FS_I(mapping->host);
+ if (vnode->flags & AFS_VNODE_DELETED) {
+ _leave(" = -ESTALE");
+ return -ESTALE;
+ }
- if (PagePrivate(page)) {
- /* We release buffers only if the entire page is being
- * invalidated.
- * The get_block cached value has been unconditionally
- * invalidated, so real IO is not possible anymore.
- */
- if (offset == 0) {
- BUG_ON(!PageLocked(page));
-
- ret = 0;
- if (!PageWriteback(page))
- ret = page->mapping->a_ops->releasepage(page,
- 0);
- /* possibly should BUG_ON(!ret); - neilb */
- }
+ /* attempt to read as many of the pages as possible */
+ ret = fscache_read_or_alloc_pages(vnode->cache,
+ mapping,
+ pages,
+ &nr_pages,
+ afs_file_readpage_read_complete,
+ NULL,
+ mapping_gfp_mask(mapping));
+
+ switch (ret) {
+ /* all pages are being read from the cache */
+ case 0:
+ BUG_ON(!list_empty(pages));
+ BUG_ON(nr_pages != 0);
+ _leave(" = 0 [reading all]");
+ return 0;
+
+ /* there were pages that couldn't be read from the cache */
+ case -ENODATA:
+ case -ENOBUFS:
+ break;
+
+ /* other error */
+ default:
+ _leave(" = %d", ret);
+ return ret;
}
- _leave(" = %d", ret);
+ /* load the missing pages from the network */
+ ret = read_cache_pages(mapping, pages, (void *) afs_readpage, file);
+
+ _leave(" = %d [netting]", ret);
+ return ret;
}
/*
@@ -279,27 +276,80 @@ static int afs_launder_page(struct page *page)
}
/*
- * release a page and cleanup its private data
+ * invalidate part or all of a page
+ * - release a page and clean up its private data if offset is 0 (indicating
+ * the entire page)
+ */
+static void afs_invalidatepage(struct page *page, unsigned long offset)
+{
+ struct afs_writeback *wb = (struct afs_writeback *) page_private(page);
+ struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
+
+ _enter("{%lu},%lu", page->index, offset);
+
+ BUG_ON(!PageLocked(page));
+
+ /* we clean up only if the entire page is being invalidated */
+ if (offset == 0) {
+ if (PageFsCache(page)) {
+ wait_on_page_fscache_write(page);
+ fscache_uncache_page(vnode->cache, page);
+ ClearPageFsCache(page);
+ }
+
+ if (PagePrivate(page)) {
+ if (wb && !PageWriteback(page)) {
+ set_page_private(page, 0);
+ afs_put_writeback(wb);
+ }
+
+ if (!page_private(page))
+ ClearPagePrivate(page);
+ }
+ }
+
+ _leave("");
+}
+
+/*
+ * release a page and clean up its private state if it's not busy
+ * - return true if the page can now be released, false if not
*/
static int afs_releasepage(struct page *page, gfp_t gfp_flags)
{
+ struct afs_writeback *wb = (struct afs_writeback *) page_private(page);
struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
- struct afs_writeback *wb;
_enter("{{%x:%u}[%lu],%lx},%x",
vnode->fid.vid, vnode->fid.vnode, page->index, page->flags,
gfp_flags);
+ /* deny if page is being written to the cache and the caller hasn't
+ * elected to wait */
+ if (PageFsCache(page)) {
+ if (PageFsCacheWrite(page)) {
+ if (!(gfp_flags & __GFP_WAIT)) {
+ _leave(" = F [cache busy]");
+ return 0;
+ }
+ wait_on_page_fscache_write(page);
+ }
+
+ fscache_uncache_page(vnode->cache, page);
+ ClearPageFsCache(page);
+ }
+
if (PagePrivate(page)) {
- wb = (struct afs_writeback *) page_private(page);
- ASSERT(wb != NULL);
- set_page_private(page, 0);
+ if (wb) {
+ set_page_private(page, 0);
+ afs_put_writeback(wb);
+ }
ClearPagePrivate(page);
- afs_put_writeback(wb);
}
- _leave(" = 0");
- return 0;
+ /* indicate that the page can be released */
+ _leave(" = T");
+ return 1;
}
/*
diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c
index 04584c0..a468f2d 100644
--- a/fs/afs/fsclient.c
+++ b/fs/afs/fsclient.c
@@ -287,6 +287,7 @@ int afs_fs_fetch_file_status(struct afs_server *server,
call->reply2 = volsync;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(FSFETCHSTATUS);
/* marshall the parameters */
bp = call->request;
@@ -316,7 +317,7 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call,
case 0:
call->offset = 0;
call->unmarshall++;
- if (call->operation_ID != FSFETCHDATA64) {
+ if (call->operation_ID != htonl(FSFETCHDATA64)) {
call->unmarshall++;
goto no_msw;
}
@@ -464,7 +465,7 @@ static int afs_fs_fetch_data64(struct afs_server *server,
call->reply3 = buffer;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
- call->operation_ID = FSFETCHDATA64;
+ call->operation_ID = htonl(FSFETCHDATA64);
/* marshall the parameters */
bp = call->request;
@@ -509,7 +510,7 @@ int afs_fs_fetch_data(struct afs_server *server,
call->reply3 = buffer;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
- call->operation_ID = FSFETCHDATA;
+ call->operation_ID = htonl(FSFETCHDATA);
/* marshall the parameters */
bp = call->request;
@@ -577,6 +578,7 @@ int afs_fs_give_up_callbacks(struct afs_server *server,
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(FSGIVEUPCALLBACKS);
/* marshall the parameters */
bp = call->request;
@@ -683,10 +685,11 @@ int afs_fs_create(struct afs_server *server,
call->reply4 = newcb;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(S_ISDIR(mode) ? FSMAKEDIR : FSCREATEFILE);
/* marshall the parameters */
bp = call->request;
- *bp++ = htonl(S_ISDIR(mode) ? FSMAKEDIR : FSCREATEFILE);
+ *bp++ = call->operation_ID;
*bp++ = htonl(vnode->fid.vid);
*bp++ = htonl(vnode->fid.vnode);
*bp++ = htonl(vnode->fid.unique);
@@ -772,10 +775,11 @@ int afs_fs_remove(struct afs_server *server,
call->reply = vnode;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(isdir ? FSREMOVEDIR : FSREMOVEFILE);
/* marshall the parameters */
bp = call->request;
- *bp++ = htonl(isdir ? FSREMOVEDIR : FSREMOVEFILE);
+ *bp++ = call->operation_ID;
*bp++ = htonl(vnode->fid.vid);
*bp++ = htonl(vnode->fid.vnode);
*bp++ = htonl(vnode->fid.unique);
@@ -857,6 +861,7 @@ int afs_fs_link(struct afs_server *server,
call->reply2 = vnode;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(FSLINK);
/* marshall the parameters */
bp = call->request;
@@ -954,6 +959,7 @@ int afs_fs_symlink(struct afs_server *server,
call->reply3 = newstatus;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(FSSYMLINK);
/* marshall the parameters */
bp = call->request;
@@ -1062,6 +1068,7 @@ int afs_fs_rename(struct afs_server *server,
call->reply2 = new_dvnode;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(FSRENAME);
/* marshall the parameters */
bp = call->request;
@@ -1178,6 +1185,7 @@ static int afs_fs_store_data64(struct afs_server *server,
call->last_to = to;
call->send_pages = true;
call->store_version = vnode->status.data_version + 1;
+ call->operation_ID = htonl(FSSTOREDATA64);
/* marshall the parameters */
bp = call->request;
@@ -1255,6 +1263,7 @@ int afs_fs_store_data(struct afs_server *server, struct afs_writeback *wb,
call->last_to = to;
call->send_pages = true;
call->store_version = vnode->status.data_version + 1;
+ call->operation_ID = htonl(FSSTOREDATA);
/* marshall the parameters */
bp = call->request;
@@ -1303,7 +1312,8 @@ static int afs_deliver_fs_store_status(struct afs_call *call,
/* unmarshall the reply once we've received all of it */
store_version = NULL;
- if (call->operation_ID == FSSTOREDATA)
+ if (call->operation_ID == htonl(FSSTOREDATA) ||
+ call->operation_ID == htonl(FSSTOREDATA64))
store_version = &call->store_version;
bp = call->buffer;
@@ -1365,7 +1375,7 @@ static int afs_fs_setattr_size64(struct afs_server *server, struct key *key,
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
call->store_version = vnode->status.data_version + 1;
- call->operation_ID = FSSTOREDATA;
+ call->operation_ID = htonl(FSSTOREDATA64);
/* marshall the parameters */
bp = call->request;
@@ -1416,7 +1426,7 @@ static int afs_fs_setattr_size(struct afs_server *server, struct key *key,
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
call->store_version = vnode->status.data_version + 1;
- call->operation_ID = FSSTOREDATA;
+ call->operation_ID = htonl(FSSTOREDATA);
/* marshall the parameters */
bp = call->request;
@@ -1462,7 +1472,7 @@ int afs_fs_setattr(struct afs_server *server, struct key *key,
call->reply = vnode;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
- call->operation_ID = FSSTORESTATUS;
+ call->operation_ID = htonl(FSSTORESTATUS);
/* marshall the parameters */
bp = call->request;
@@ -1742,6 +1752,7 @@ int afs_fs_get_volume_status(struct afs_server *server,
call->reply3 = tmpbuf;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(FSGETVOLUMESTATUS);
/* marshall the parameters */
bp = call->request;
@@ -1828,6 +1839,7 @@ int afs_fs_set_lock(struct afs_server *server,
call->reply = vnode;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(FSSETLOCK);
/* marshall the parameters */
bp = call->request;
@@ -1861,6 +1873,7 @@ int afs_fs_extend_lock(struct afs_server *server,
call->reply = vnode;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(FSEXTENDLOCK);
/* marshall the parameters */
bp = call->request;
@@ -1893,6 +1906,7 @@ int afs_fs_release_lock(struct afs_server *server,
call->reply = vnode;
call->service_id = FS_SERVICE;
call->port = htons(AFS_FS_PORT);
+ call->operation_ID = htonl(FSRELEASELOCK);
/* marshall the parameters */
bp = call->request;
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index d196840..0f22d56 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -61,6 +61,9 @@ static int afs_inode_map_status(struct afs_vnode *vnode, struct key *key)
return -EBADMSG;
}
+ if (vnode->status.size != inode->i_size)
+ fscache_attr_changed(vnode->cache);
+
inode->i_nlink = vnode->status.nlink;
inode->i_uid = vnode->status.owner;
inode->i_gid = 0;
@@ -149,15 +152,6 @@ struct inode *afs_iget(struct super_block *sb, struct key *key,
return inode;
}
-#ifdef AFS_CACHING_SUPPORT
- /* set up caching before reading the status, as fetch-status reads the
- * first page of symlinks to see if they're really mntpts */
- cachefs_acquire_cookie(vnode->volume->cache,
- NULL,
- vnode,
- &vnode->cache);
-#endif
-
if (!status) {
/* it's a remotely extant inode */
set_bit(AFS_VNODE_CB_BROKEN, &vnode->flags);
@@ -183,6 +177,13 @@ struct inode *afs_iget(struct super_block *sb, struct key *key,
}
}
+ /* set up caching before mapping the status, as map-status reads the
+ * first page of symlinks to see if they're really mountpoints */
+ inode->i_size = vnode->status.size;
+ vnode->cache = fscache_acquire_cookie(vnode->volume->cache,
+ &afs_vnode_cache_index_def,
+ vnode);
+
ret = afs_inode_map_status(vnode, key);
if (ret < 0)
goto bad_inode;
@@ -196,6 +197,8 @@ struct inode *afs_iget(struct super_block *sb, struct key *key,
/* failure */
bad_inode:
+ fscache_relinquish_cookie(vnode->cache, 0);
+ vnode->cache = NULL;
make_bad_inode(inode);
unlock_new_inode(inode);
iput(inode);
@@ -342,10 +345,8 @@ void afs_clear_inode(struct inode *inode)
ASSERT(list_empty(&vnode->writebacks));
ASSERT(!vnode->cb_promised);
-#ifdef AFS_CACHING_SUPPORT
- cachefs_relinquish_cookie(vnode->cache, 0);
+ fscache_relinquish_cookie(vnode->cache, 0);
vnode->cache = NULL;
-#endif
mutex_lock(&vnode->permits_lock);
permits = vnode->permits;
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 12afccc..65d307a 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -21,6 +21,7 @@
#include "afs.h"
#include "afs_vl.h"
+#include "cache.h"
#define AFS_CELL_MAX_ADDRS 15
@@ -194,9 +195,7 @@ struct afs_cell {
struct key *anonymous_key; /* anonymous user key for this cell */
struct list_head proc_link; /* /proc cell list link */
struct proc_dir_entry *proc_dir; /* /proc dir for this cell */
-#ifdef AFS_CACHING_SUPPORT
- struct cachefs_cookie *cache; /* caching cookie */
-#endif
+ struct fscache_cookie *cache; /* caching cookie */
/* server record management */
rwlock_t servers_lock; /* active server list lock */
@@ -250,9 +249,7 @@ struct afs_vlocation {
struct list_head grave; /* link in master graveyard list */
struct list_head update; /* link in master update list */
struct afs_cell *cell; /* cell to which volume belongs */
-#ifdef AFS_CACHING_SUPPORT
- struct cachefs_cookie *cache; /* caching cookie */
-#endif
+ struct fscache_cookie *cache; /* caching cookie */
struct afs_cache_vlocation vldb; /* volume information DB record */
struct afs_volume *vols[3]; /* volume access record pointer (index by type) */
wait_queue_head_t waitq; /* status change waitqueue */
@@ -303,9 +300,7 @@ struct afs_volume {
atomic_t usage;
struct afs_cell *cell; /* cell to which belongs (unrefd ptr) */
struct afs_vlocation *vlocation; /* volume location */
-#ifdef AFS_CACHING_SUPPORT
- struct cachefs_cookie *cache; /* caching cookie */
-#endif
+ struct fscache_cookie *cache; /* caching cookie */
afs_volid_t vid; /* volume ID */
afs_voltype_t type; /* type of volume */
char type_force; /* force volume type (suppress R/O -> R/W) */
@@ -334,9 +329,7 @@ struct afs_vnode {
struct afs_server *server; /* server currently supplying this file */
struct afs_fid fid; /* the file identifier for this inode */
struct afs_file_status status; /* AFS status info for this file */
-#ifdef AFS_CACHING_SUPPORT
- struct cachefs_cookie *cache; /* caching cookie */
-#endif
+ struct fscache_cookie *cache; /* caching cookie */
struct afs_permits *permits; /* cache of permits so far obtained */
struct mutex permits_lock; /* lock for altering permits list */
struct mutex validate_lock; /* lock for validating this vnode */
@@ -429,6 +422,22 @@ struct afs_uuid {
/*****************************************************************************/
/*
+ * cache.c
+ */
+#ifdef CONFIG_AFS_FSCACHE
+extern struct fscache_netfs afs_cache_netfs;
+extern struct fscache_cookie_def afs_cell_cache_index_def;
+extern struct fscache_cookie_def afs_vlocation_cache_index_def;
+extern struct fscache_cookie_def afs_volume_cache_index_def;
+extern struct fscache_cookie_def afs_vnode_cache_index_def;
+#else
+#define afs_cell_cache_index_def (*(struct fscache_cookie_def *) NULL)
+#define afs_vlocation_cache_index_def (*(struct fscache_cookie_def *) NULL)
+#define afs_volume_cache_index_def (*(struct fscache_cookie_def *) NULL)
+#define afs_vnode_cache_index_def (*(struct fscache_cookie_def *) NULL)
+#endif
+
+/*
* callback.c
*/
extern void afs_init_callback_state(struct afs_server *);
@@ -447,9 +456,6 @@ extern void afs_callback_update_kill(void);
*/
extern struct rw_semaphore afs_proc_cells_sem;
extern struct list_head afs_proc_cells;
-#ifdef AFS_CACHING_SUPPORT
-extern struct cachefs_index_def afs_cache_cell_index_def;
-#endif
#define afs_get_cell(C) do { atomic_inc(&(C)->usage); } while(0)
extern int afs_cell_init(char *);
@@ -557,9 +563,6 @@ extern void afs_clear_inode(struct inode *);
* main.c
*/
extern struct afs_uuid afs_uuid;
-#ifdef AFS_CACHING_SUPPORT
-extern struct cachefs_netfs afs_cache_netfs;
-#endif
/*
* misc.c
@@ -642,10 +645,6 @@ extern int afs_get_MAC_address(u8 *, size_t);
/*
* vlclient.c
*/
-#ifdef AFS_CACHING_SUPPORT
-extern struct cachefs_index_def afs_vlocation_cache_index_def;
-#endif
-
extern int afs_vl_get_entry_by_name(struct in_addr *, struct key *,
const char *, struct afs_cache_vlocation *,
const struct afs_wait_mode *);
@@ -669,12 +668,6 @@ extern void afs_vlocation_purge(void);
/*
* vnode.c
*/
-#ifdef AFS_CACHING_SUPPORT
-extern struct cachefs_index_def afs_vnode_cache_index_def;
-#endif
-
-extern struct afs_timer_ops afs_vnode_cb_timed_out_ops;
-
static inline struct afs_vnode *AFS_FS_I(struct inode *inode)
{
return container_of(inode, struct afs_vnode, vfs_inode);
@@ -716,10 +709,6 @@ extern int afs_vnode_release_lock(struct afs_vnode *, struct key *);
/*
* volume.c
*/
-#ifdef AFS_CACHING_SUPPORT
-extern struct cachefs_index_def afs_volume_cache_index_def;
-#endif
-
#define afs_get_volume(V) do { atomic_inc(&(V)->usage); } while(0)
extern void afs_put_volume(struct afs_volume *);
diff --git a/fs/afs/main.c b/fs/afs/main.c
index 0f60f6b..f04b838 100644
--- a/fs/afs/main.c
+++ b/fs/afs/main.c
@@ -1,6 +1,6 @@
/* AFS client file system
*
- * Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
+ * Copyright (C) 2002,5 Red Hat, Inc. All Rights Reserved.
* Written by David Howells ([email protected])
*
* This program is free software; you can redistribute it and/or
@@ -29,18 +29,6 @@ static char *rootcell;
module_param(rootcell, charp, 0);
MODULE_PARM_DESC(rootcell, "root AFS cell name and VL server IP addr list");
-#ifdef AFS_CACHING_SUPPORT
-static struct cachefs_netfs_operations afs_cache_ops = {
- .get_page_cookie = afs_cache_get_page_cookie,
-};
-
-struct cachefs_netfs afs_cache_netfs = {
- .name = "afs",
- .version = 0,
- .ops = &afs_cache_ops,
-};
-#endif
-
struct afs_uuid afs_uuid;
/*
@@ -104,10 +92,9 @@ static int __init afs_init(void)
if (ret < 0)
return ret;
-#ifdef AFS_CACHING_SUPPORT
+#ifdef CONFIG_AFS_FSCACHE
/* we want to be able to cache */
- ret = cachefs_register_netfs(&afs_cache_netfs,
- &afs_cache_cell_index_def);
+ ret = fscache_register_netfs(&afs_cache_netfs);
if (ret < 0)
goto error_cache;
#endif
@@ -142,8 +129,8 @@ error_fs:
error_open_socket:
error_vl_update_init:
error_cell_init:
-#ifdef AFS_CACHING_SUPPORT
- cachefs_unregister_netfs(&afs_cache_netfs);
+#ifdef CONFIG_AFS_FSCACHE
+ fscache_unregister_netfs(&afs_cache_netfs);
error_cache:
#endif
afs_callback_update_kill();
@@ -175,8 +162,8 @@ static void __exit afs_exit(void)
afs_vlocation_purge();
flush_scheduled_work();
afs_cell_purge();
-#ifdef AFS_CACHING_SUPPORT
- cachefs_unregister_netfs(&afs_cache_netfs);
+#ifdef CONFIG_AFS_FSCACHE
+ fscache_unregister_netfs(&afs_cache_netfs);
#endif
afs_proc_cleanup();
rcu_barrier();
diff --git a/fs/afs/mntpt.c b/fs/afs/mntpt.c
index 6f8c96f..589d158 100644
--- a/fs/afs/mntpt.c
+++ b/fs/afs/mntpt.c
@@ -173,9 +173,9 @@ static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt)
if (PageError(page))
goto error;
- buf = kmap(page);
+ buf = kmap_atomic(page, KM_USER0);
memcpy(devname, buf, size);
- kunmap(page);
+ kunmap_atomic(buf, KM_USER0);
page_cache_release(page);
page = NULL;
diff --git a/fs/afs/rxrpc.c b/fs/afs/rxrpc.c
index 8ccee9e..080e285 100644
--- a/fs/afs/rxrpc.c
+++ b/fs/afs/rxrpc.c
@@ -335,6 +335,7 @@ int afs_make_call(struct in_addr *addr, struct afs_call *call, gfp_t gfp,
/* create a call */
rxcall = rxrpc_kernel_begin_call(afs_socket, &srx, call->key,
+ ntohl(call->operation_ID),
(unsigned long) call, gfp);
call->key = NULL;
if (IS_ERR(rxcall)) {
diff --git a/fs/afs/vlclient.c b/fs/afs/vlclient.c
index 36c1306..60ddd4f 100644
--- a/fs/afs/vlclient.c
+++ b/fs/afs/vlclient.c
@@ -170,6 +170,7 @@ int afs_vl_get_entry_by_name(struct in_addr *addr,
call->reply = entry;
call->service_id = VL_SERVICE;
call->port = htons(AFS_VL_PORT);
+ call->operation_ID = htonl(VLGETENTRYBYNAME);
/* marshall the parameters */
bp = call->request;
@@ -206,6 +207,7 @@ int afs_vl_get_entry_by_id(struct in_addr *addr,
call->reply = entry;
call->service_id = VL_SERVICE;
call->port = htons(AFS_VL_PORT);
+ call->operation_ID = htonl(VLGETENTRYBYID);
/* marshall the parameters */
bp = call->request;
diff --git a/fs/afs/vlocation.c b/fs/afs/vlocation.c
index 09e3ad0..f9ec766 100644
--- a/fs/afs/vlocation.c
+++ b/fs/afs/vlocation.c
@@ -281,10 +281,7 @@ static void afs_vlocation_apply_update(struct afs_vlocation *vl,
vl->vldb = *vldb;
-#ifdef AFS_CACHING_SUPPORT
- /* update volume entry in local cache */
- cachefs_update_cookie(vl->cache);
-#endif
+ fscache_update_cookie(vl->cache);
}
/*
@@ -304,12 +301,8 @@ static int afs_vlocation_fill_in_record(struct afs_vlocation *vl,
memset(&vldb, 0, sizeof(vldb));
/* see if we have an in-cache copy (will set vl->valid if there is) */
-#ifdef AFS_CACHING_SUPPORT
- cachefs_acquire_cookie(cell->cache,
- &afs_volume_cache_index_def,
- vlocation,
- &vl->cache);
-#endif
+ vl->cache = fscache_acquire_cookie(vl->cell->cache,
+ &afs_vlocation_cache_index_def, vl);
if (vl->valid) {
/* try to update a known volume in the cell VL databases by
@@ -420,6 +413,9 @@ fill_in_record:
spin_unlock(&vl->lock);
wake_up(&vl->waitq);
+ /* update volume entry in local cache */
+ fscache_update_cookie(vl->cache);
+
/* schedule for regular updates */
afs_vlocation_queue_for_updates(vl);
goto success;
@@ -465,7 +461,7 @@ found_in_memory:
spin_unlock(&vl->lock);
success:
- _leave(" = %p",vl);
+ _leave(" = %p", vl);
return vl;
error_abandon:
@@ -523,10 +519,7 @@ static void afs_vlocation_destroy(struct afs_vlocation *vl)
{
_enter("%p", vl);
-#ifdef AFS_CACHING_SUPPORT
- cachefs_relinquish_cookie(vl->cache, 0);
-#endif
-
+ fscache_relinquish_cookie(vl->cache, 0);
afs_put_cell(vl->cell);
kfree(vl);
}
diff --git a/fs/afs/volume.c b/fs/afs/volume.c
index 8bab0e3..2cc3dab 100644
--- a/fs/afs/volume.c
+++ b/fs/afs/volume.c
@@ -124,13 +124,9 @@ struct afs_volume *afs_volume_lookup(struct afs_mount_params *params)
}
/* attach the cache and volume location */
-#ifdef AFS_CACHING_SUPPORT
- cachefs_acquire_cookie(vlocation->cache,
- &afs_vnode_cache_index_def,
- volume,
- &volume->cache);
-#endif
-
+ volume->cache = fscache_acquire_cookie(vlocation->cache,
+ &afs_volume_cache_index_def,
+ volume);
afs_get_vlocation(vlocation);
volume->vlocation = vlocation;
@@ -194,9 +190,7 @@ void afs_put_volume(struct afs_volume *volume)
up_write(&vlocation->cell->vl_sem);
/* finish cleaning up the volume */
-#ifdef AFS_CACHING_SUPPORT
- cachefs_relinquish_cookie(volume->cache, 0);
-#endif
+ fscache_relinquish_cookie(volume->cache, 0);
afs_put_vlocation(vlocation);
for (loop = volume->nservers - 1; loop >= 0; loop--)
diff --git a/fs/afs/write.c b/fs/afs/write.c
index dd471f0..c5ce221 100644
--- a/fs/afs/write.c
+++ b/fs/afs/write.c
@@ -261,7 +261,6 @@ flush_conflicting_wb:
_debug("reuse");
afs_put_writeback(wb);
set_page_private(page, 0);
- ClearPagePrivate(page);
goto try_again;
}
@@ -694,7 +693,6 @@ void afs_pages_written_back(struct afs_vnode *vnode, struct afs_call *call)
end_page_writeback(page);
if (page_private(page) == (unsigned long) wb) {
set_page_private(page, 0);
- ClearPagePrivate(page);
wb->usage--;
}
}
@@ -854,6 +852,10 @@ int afs_page_mkwrite(struct vm_area_struct *vma, struct page *page)
_enter("{{%x:%u},%x},{%lx}",
vnode->fid.vid, vnode->fid.vnode, key_serial(key), page->index);
+ /* wait for the page to be written to the cache before we allow it to
+ * be modified */
+ wait_on_page_fscache_write(page);
+
do {
lock_page(page);
if (page->mapping == vma->vm_file->f_mapping)
--- David Howells <[email protected]> wrote:
>
>
> Hi Al, Christoph, Trond, Stephen, Casey,
>
> Here's a set of patches that implement a very basic set of COW credentials.
> It
> compiles, links and runs for x86_64 with EXT3, (V)FAT, NFS, AFS, SELinux and
> keyrings all enabled. Most other filesystems are disabled, apart from things
> like proc. It is not intended to completely cover the kernel at this point.
>
> The cred struct contains the credentials that the kernel needs to act upon
> something or to create something. Credentials that govern how a task may be
> acted upon remain in the task struct.
>
> In essence, the introduction of the cred struct separates a task's subjective
> context (the authority with which it acts) from its objective context (the
> authorisation required by others that want to act upon it), and permits
> overriding of the subjective context by a kernel service so that the service
> can act on the task's behalf to do something the task couldn't do on its own
> authority.
>
> Because keyrings and effective capabilities can be installed or changed in
> one
> process by another process, they are shadowed by the cred structure rather
> than
> residing there. Additionally, the session and process keyrings are shared
> between all the threads of a process. The shadowing is performed by
> update_current_cred() which is invoked on entry to any system call that might
> need it.
>
> A thread's cred struct may be read by that thread without any RCU precautions
> as only that thread may replace the its own cred struct. To change a
> thread's
> credentials, dup_cred() should be called to create a new copy, the copy
> should
> be changed, and then set_current_cred() should be called to make it live.
> Once
> live, it may not be changed as it may then be shared with file descriptors,
> RPC
> calls and other threads. RCU will be used to dispose of the old structure.
>
>
> The four patches are:
>
> (1) Introduce struct cred and migrate fsuid, fsgid, the groups list and the
> keyrings pointer to it.
>
> (2) Introduce a security pointer into the cred struct and add LSM hooks to
> duplicate the information pointed to thereby and to free it.
>
> Make SELinux implement the hooks, splitting out some the task security
> data to be associated with struct cred instead.
>
> (3) Migrate the effective capabilities mask into the cred struct.
>
> (4) Provide a pair of LSM hooks so that a kernel service can (a) get a
> credential record representing the authority with which it is permitted
> to
> act, and (b) alter the file creation context in a credential record.
>
> In addition, as this works with cachefiles, I've included all the FS-Cache,
> CacheFiles, NFS and AFS patches.
>
> To substitute a temporary set of credentials, the cred struct attached to the
> task should be altered, like so:
>
> int get_privileged_creds(...)
> {
> /* get special privileged creds */
> my_special_cred = get_kernel_cred("cachefiles", current);
> change_create_files_as(my_special_cred, my_cache_dir);
> }
>
> int do_stuff(...)
> {
> struct cred *cred;
>
> /* rotate in the new creds, saving the old */
> cred = __set_current_cred(get_cred(my_special_cred));
>
> do_privileged_stuff();
>
> /* restore the old creds */
> set_current_cred(cred);
> }
>
> One thing I'm not certain about is how this should interact with /proc, which
> can display some of the stuff in the cred struct. I think it may be
> necessary
> to have a real cred pointer and an effective cred pointer, with the contents
> of
> /proc coming from the real, but the effective governing what actually goes
> on.
I think you want the effective values to show up in /proc.
Casey Schaufler
[email protected]
Casey Schaufler <[email protected]> wrote:
> > One thing I'm not certain about is how this should interact with /proc,
> > which can display some of the stuff in the cred struct. I think it may be
> > necessary to have a real cred pointer and an effective cred pointer, with
> > the contents of /proc coming from the real, but the effective governing
> > what actually goes on.
>
> I think you want the effective values to show up in /proc.
Perhaps - but bear in mind that in the override case they weren't set by the
process itself.
David
--- David Howells <[email protected]> wrote:
> Casey Schaufler <[email protected]> wrote:
>
> > > One thing I'm not certain about is how this should interact with /proc,
> > > which can display some of the stuff in the cred struct. I think it may
> be
> > > necessary to have a real cred pointer and an effective cred pointer, with
> > > the contents of /proc coming from the real, but the effective governing
> > > what actually goes on.
> >
> > I think you want the effective values to show up in /proc.
>
> Perhaps - but bear in mind that in the override case they weren't set by the
> process itself.
They are nonetheless in effect and (heaven forbid) should they be
abused you don't want to hide the facts from concerned observers.
Casey Schaufler
[email protected]
David Howells wrote:
> The attached patch makes it possible for the NFS filesystem to make use of the
> network filesystem local caching service (FS-Cache).
>
> To be able to use this, an updated mount program is required. This can be
> obtained from:
>
> http://people.redhat.com/steved/fscache/util-linux/
>
> To mount an NFS filesystem to use caching, add an "fsc" option to the mount:
>
> mount warthog:/ /a -o fsc
>
> Signed-Off-By: David Howells <[email protected]>
> ---
Did I miss the section where the modified semantics about which
mounted file systems can use the cache and which ones can not
was implemented? For example, mounts of the same file system
from the server with "fsc", but with different mount options
such as "rw" or "ro" or NFS dependent mount options, must fail
because of the way that the cache is accessed. Also, perhaps
a little confusing, that mounts of different paths on a server
which land on the same mounted file system on the server, but
with these differing mount options must also fail?
Thanx...
ps
On Fri, 2007-09-21 at 15:47 +0100, David Howells wrote:
> Add an address space operation to write one single page of data to an inode at
> a page-aligned location (thus permitting the implementation to be highly
> optimised).
>
> This is used by CacheFiles to store the contents of netfs pages into their
> backing file pages.
>
> Supply a generic implementation for this that uses the prepare_write() and
> commit_write() address_space operations to bound a copy directly into the page
> cache.
>
> Hook the Ext2 and Ext3 operations to the generic implementation.
So why do you need a new address space operation? AFAICS the generic
implementation will work for pretty much everyone who supports the
existing prepare_write()/commit_write().
Furthermore, you don't appear to supply any alternative "optimised"
implementations...
Trond
> Signed-Off-By: David Howells <[email protected]>
> ---
>
> fs/ext2/inode.c | 2 +
> fs/ext3/inode.c | 3 ++
> include/linux/fs.h | 7 ++++
> mm/filemap.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 107 insertions(+), 0 deletions(-)
>
> diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
> index 0079b2c..b3e4b50 100644
> --- a/fs/ext2/inode.c
> +++ b/fs/ext2/inode.c
> @@ -695,6 +695,7 @@ const struct address_space_operations ext2_aops = {
> .direct_IO = ext2_direct_IO,
> .writepages = ext2_writepages,
> .migratepage = buffer_migrate_page,
> + .write_one_page = generic_file_buffered_write_one_page,
> };
>
> const struct address_space_operations ext2_aops_xip = {
> @@ -713,6 +714,7 @@ const struct address_space_operations ext2_nobh_aops = {
> .direct_IO = ext2_direct_IO,
> .writepages = ext2_writepages,
> .migratepage = buffer_migrate_page,
> + .write_one_page = generic_file_buffered_write_one_page,
> };
>
> /*
> diff --git a/fs/ext3/inode.c b/fs/ext3/inode.c
> index de4e316..93809eb 100644
> --- a/fs/ext3/inode.c
> +++ b/fs/ext3/inode.c
> @@ -1713,6 +1713,7 @@ static const struct address_space_operations ext3_ordered_aops = {
> .releasepage = ext3_releasepage,
> .direct_IO = ext3_direct_IO,
> .migratepage = buffer_migrate_page,
> + .write_one_page = generic_file_buffered_write_one_page,
> };
>
> static const struct address_space_operations ext3_writeback_aops = {
> @@ -1727,6 +1728,7 @@ static const struct address_space_operations ext3_writeback_aops = {
> .releasepage = ext3_releasepage,
> .direct_IO = ext3_direct_IO,
> .migratepage = buffer_migrate_page,
> + .write_one_page = generic_file_buffered_write_one_page,
> };
>
> static const struct address_space_operations ext3_journalled_aops = {
> @@ -1740,6 +1742,7 @@ static const struct address_space_operations ext3_journalled_aops = {
> .bmap = ext3_bmap,
> .invalidatepage = ext3_invalidatepage,
> .releasepage = ext3_releasepage,
> + .write_one_page = generic_file_buffered_write_one_page,
> };
>
> void ext3_set_aops(struct inode *inode)
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index bf35441..38f67ac 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -434,6 +434,11 @@ struct address_space_operations {
> int (*migratepage) (struct address_space *,
> struct page *, struct page *);
> int (*launder_page) (struct page *);
> + /* write the contents of the source page over the page at the specified
> + * index in the target address space (the source page does not need to
> + * be related to the target address space) */
> + int (*write_one_page)(struct address_space *, pgoff_t, struct page *);
> +
> };
>
> struct backing_dev_info;
> @@ -1669,6 +1674,8 @@ extern ssize_t generic_file_direct_write(struct kiocb *, const struct iovec *,
> unsigned long *, loff_t, loff_t *, size_t, size_t);
> extern ssize_t generic_file_buffered_write(struct kiocb *, const struct iovec *,
> unsigned long, loff_t, loff_t *, size_t, ssize_t);
> +extern int generic_file_buffered_write_one_page(struct address_space *,
> + pgoff_t, struct page *);
> extern ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos);
> extern ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos);
> extern void do_generic_mapping_read(struct address_space *mapping,
> diff --git a/mm/filemap.c b/mm/filemap.c
> index 3a61923..21aeee9 100644
> --- a/mm/filemap.c
> +++ b/mm/filemap.c
> @@ -2016,6 +2016,101 @@ zero_length_segment:
> }
> EXPORT_SYMBOL(generic_file_buffered_write);
>
> +/**
> + * generic_file_buffered_write_one_page - Write a single page of data to an
> + * inode
> + * @mapping - The address space of the target inode
> + * @index - The target page in the target inode to fill
> + * @source - The data to write into the target page
> + *
> + * Write the data from the source page to the page in the nominated address
> + * space at the @index specified. Note that the file will not be extended if
> + * the page crosses the EOF marker, in which case only the first part of the
> + * page will be written.
> + *
> + * The @source page does not need to have any association with the file or the
> + * target page offset.
> + */
> +int generic_file_buffered_write_one_page(struct address_space *mapping,
> + pgoff_t index,
> + struct page *source)
> +{
> + const struct address_space_operations *a_ops = mapping->a_ops;
> + struct pagevec lru_pvec;
> + struct page *page, *cached_page = NULL;
> + unsigned psize;
> + loff_t isize;
> + long status = 0;
> +
> + pagevec_init(&lru_pvec, 0);
> +
> + page = __grab_cache_page(mapping, index, &cached_page, &lru_pvec);
> + if (!page) {
> + BUG_ON(cached_page);
> + return -ENOMEM;
> + }
> +
> + psize = PAGE_CACHE_SIZE;
> + isize = i_size_read(mapping->host);
> + if ((isize >> PAGE_SHIFT) == index)
> + psize = isize & (PAGE_SIZE - 1);
> +
> + status = a_ops->prepare_write(NULL, page, 0, psize);
> + if (unlikely(status)) {
> + isize = i_size_read(mapping->host);
> +
> + if (status != AOP_TRUNCATED_PAGE)
> + unlock_page(page);
> + page_cache_release(page);
> + if (status == AOP_TRUNCATED_PAGE)
> + goto sync;
> +
> + /* prepare_write() may have instantiated a few blocks outside
> + * i_size. Trim these off again.
> + */
> + if ((1ULL << (index + 1)) > isize)
> + vmtruncate(mapping->host, isize);
> + goto sync;
> + }
> +
> + copy_highpage(page, source);
> + flush_dcache_page(page);
> +
> + status = a_ops->commit_write(NULL, page, 0, psize);
> + if (status == AOP_TRUNCATED_PAGE) {
> + page_cache_release(page);
> + goto sync;
> + }
> +
> + if (status > 0)
> + status = 0;
> +
> + unlock_page(page);
> + mark_page_accessed(page);
> + page_cache_release(page);
> + if (status < 0)
> + return status;
> +
> + balance_dirty_pages_ratelimited(mapping);
> + cond_resched();
> +
> +sync:
> + if (cached_page)
> + page_cache_release(cached_page);
> +
> + /* the caller must handle O_SYNC themselves, but we handle S_SYNC and
> + * MS_SYNCHRONOUS here */
> + if (unlikely(IS_SYNC(mapping->host)) && !a_ops->writepage)
> + status = generic_osync_inode(mapping->host, mapping,
> + OSYNC_METADATA | OSYNC_DATA);
> +
> + /* the caller must handle O_DIRECT for themselves */
> +
> + pagevec_lru_add(&lru_pvec);
> + return status;
> +}
> +EXPORT_SYMBOL(generic_file_buffered_write_one_page);
> +
> static ssize_t
> __generic_file_aio_write_nolock(struct kiocb *iocb, const struct iovec *iov,
> unsigned long nr_segs, loff_t *ppos)
On Fri, 2007-09-21 at 15:48 +0100, David Howells wrote:
> Add a function to install a monitor on the page lock waitqueue for a particular
> page, thus allowing the page being unlocked to be detected.
>
> This is used by CacheFiles to detect read completion on a page in the backing
> filesystem so that it can then copy the data to the waiting netfs page.
Won't it in any case want to lock the page too? That would be the only
way to ensure that the page is still mapped into the address space when
you're writing it out...
Trond
> Signed-Off-By: David Howells <[email protected]>
> ---
>
> include/linux/pagemap.h | 5 +++++
> mm/filemap.c | 19 +++++++++++++++++++
> 2 files changed, 24 insertions(+), 0 deletions(-)
>
> diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
> index d1049b6..452fdcf 100644
> --- a/include/linux/pagemap.h
> +++ b/include/linux/pagemap.h
> @@ -220,6 +220,11 @@ static inline void wait_on_page_fscache_write(struct page *page)
> extern void end_page_fscache_write(struct page *page);
>
> /*
> + * Add an arbitrary waiter to a page's wait queue
> + */
> +extern void add_page_wait_queue(struct page *page, wait_queue_t *waiter);
> +
> +/*
> * Fault a userspace page into pagetables. Return non-zero on a fault.
> *
> * This assumes that two userspace pages are always sufficient. That's
> diff --git a/mm/filemap.c b/mm/filemap.c
> index 21aeee9..e48e862 100644
> --- a/mm/filemap.c
> +++ b/mm/filemap.c
> @@ -518,6 +518,25 @@ void fastcall wait_on_page_bit(struct page *page, int bit_nr)
> EXPORT_SYMBOL(wait_on_page_bit);
>
> /**
> + * add_page_wait_queue - Add an arbitrary waiter to a page's wait queue
> + * @page - Page defining the wait queue of interest
> + * @waiter - Waiter to add to the queue
> + *
> + * Add an arbitrary @waiter to the wait queue for the nominated @page.
> + */
> +void add_page_wait_queue(struct page *page, wait_queue_t *waiter)
> +{
> + wait_queue_head_t *q = page_waitqueue(page);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&q->lock, flags);
> + __add_wait_queue(q, waiter);
> + spin_unlock_irqrestore(&q->lock, flags);
> +}
> +
> +EXPORT_SYMBOL_GPL(add_page_wait_queue);
> +
> +/**
> * unlock_page - unlock a locked page
> * @page: the page
> *
Provide an add_wait_queue_tail() function to add a waiter to the back of a
wait queue instead of the front.
Signed-off-by: David Howells <[email protected]>
---
include/linux/wait.h | 1 +
kernel/wait.c | 18 ++++++++++++++++++
2 files changed, 19 insertions(+), 0 deletions(-)
diff --git a/include/linux/wait.h b/include/linux/wait.h
index 0e68628..4cae7db 100644
--- a/include/linux/wait.h
+++ b/include/linux/wait.h
@@ -118,6 +118,7 @@ static inline int waitqueue_active(wait_queue_head_t *q)
#define is_sync_wait(wait) (!(wait) || ((wait)->private))
extern void FASTCALL(add_wait_queue(wait_queue_head_t *q, wait_queue_t * wait));
+extern void FASTCALL(add_wait_queue_tail(wait_queue_head_t *q, wait_queue_t * wait));
extern void FASTCALL(add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t * wait));
extern void FASTCALL(remove_wait_queue(wait_queue_head_t *q, wait_queue_t * wait));
diff --git a/kernel/wait.c b/kernel/wait.c
index 444ddbf..7acc9cc 100644
--- a/kernel/wait.c
+++ b/kernel/wait.c
@@ -29,6 +29,24 @@ void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
}
EXPORT_SYMBOL(add_wait_queue);
+/**
+ * add_wait_queue_tail - Add a waiter to the back of a waitqueue
+ * @q: the wait queue to append the waiter to
+ * @wait: the waiter to be queued
+ *
+ * Add a waiter to the back of a waitqueue so that it gets woken up last.
+ */
+void fastcall add_wait_queue_tail(wait_queue_head_t *q, wait_queue_t *wait)
+{
+ unsigned long flags;
+
+ wait->flags &= ~WQ_FLAG_EXCLUSIVE;
+ spin_lock_irqsave(&q->lock, flags);
+ __add_wait_queue_tail(q, wait);
+ spin_unlock_irqrestore(&q->lock, flags);
+}
+EXPORT_SYMBOL(add_wait_queue_tail);
+
void fastcall add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
This patch set is available for download as a tarball from:
http://people.redhat.com/~dhowells/nfs/nfs+fscache-23.tar.bz2
David
This one-line patch fixes the missing export of copy_page introduced
by the cachefile patches. This patch is not yet upstream, but is required
for cachefile on ia64. It will be pushed upstream when cachefile goes
upstream.
Signed-off-by: Prarit Bhargava <[email protected]>
Signed-Off-By: David Howells <[email protected]>
---
arch/ia64/kernel/ia64_ksyms.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/arch/ia64/kernel/ia64_ksyms.c b/arch/ia64/kernel/ia64_ksyms.c
index bd17190..20c3546 100644
--- a/arch/ia64/kernel/ia64_ksyms.c
+++ b/arch/ia64/kernel/ia64_ksyms.c
@@ -43,6 +43,7 @@ EXPORT_SYMBOL(__do_clear_user);
EXPORT_SYMBOL(__strlen_user);
EXPORT_SYMBOL(__strncpy_from_user);
EXPORT_SYMBOL(__strnlen_user);
+EXPORT_SYMBOL(copy_page);
/* from arch/ia64/lib */
extern void __divsi3(void);
Request a credential record for the named kernel service. This produces a
cred struct with appropriate DAC and MAC controls for effecting that service.
It may be used to override the credentials on a task to do work on that task's
behalf.
Signed-off-by: David Howells <[email protected]>
---
include/linux/cred.h | 2 +
include/linux/security.h | 45 ++++++++++++++++++++++++++++++
kernel/cred.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++
security/dummy.c | 13 +++++++++
security/selinux/hooks.c | 47 ++++++++++++++++++++++++++++++++
5 files changed, 175 insertions(+), 0 deletions(-)
diff --git a/include/linux/cred.h b/include/linux/cred.h
index f2df1c3..fcbfc89 100644
--- a/include/linux/cred.h
+++ b/include/linux/cred.h
@@ -49,6 +49,8 @@ extern void change_fsgid(struct cred *, gid_t);
extern void change_groups(struct cred *, struct group_info *);
extern void change_cap(struct cred *, kernel_cap_t);
extern struct cred *dup_cred(const struct cred *);
+extern struct cred *get_kernel_cred(const char *, struct task_struct *);
+extern int change_create_files_as(struct cred *, struct inode *);
/**
* get_cred - Get an extra reference on a credentials record
diff --git a/include/linux/security.h b/include/linux/security.h
index 74cc204..7f11e6d 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -514,6 +514,20 @@ struct request_sock;
* @cred_destroy:
* Destroy the credentials attached to a cred structure.
* @cred points to the credentials structure that is to be destroyed.
+ * @cred_kernel_act_as:
+ * Set the credentials for a kernel service to act as (subjective context).
+ * @cred points to the credentials structure to be filled in.
+ * @service names the service making the request.
+ * @daemon: A userspace daemon to be used as a base for the context.
+ * @dentry: A file or dir to be used as a base for the file creation
+ * context.
+ * Return 0 if successful.
+ * @cred_create_files_as:
+ * Set the file creation context in a credentials record to be the same as
+ * the objective context of an inode.
+ * @cred points to the credentials structure to be altered.
+ * @inode points to the inode to use as a reference.
+ * Return 0 if successful.
*
* Security hooks for task operations.
*
@@ -1270,6 +1284,9 @@ struct security_operations {
int (*cred_dup)(struct cred *cred);
void (*cred_destroy)(struct cred *cred);
+ int (*cred_kernel_act_as)(struct cred *cred, const char *service,
+ struct task_struct *daemon);
+ int (*cred_create_files_as)(struct cred *cred, struct inode *inode);
int (*task_create) (unsigned long clone_flags);
int (*task_alloc_security) (struct task_struct * p);
@@ -1888,6 +1905,21 @@ static inline void security_cred_destroy(struct cred *cred)
return security_ops->cred_destroy(cred);
}
+static inline int security_cred_kernel_act_as(struct cred *cred,
+ const char *service,
+ struct task_struct *daemon)
+{
+ return security_ops->cred_kernel_act_as(cred, service, daemon);
+}
+
+static inline int security_cred_create_files_as(struct cred *cred,
+ struct inode *inode)
+{
+ if (IS_PRIVATE(inode))
+ return -EINVAL;
+ return security_ops->cred_create_files_as(cred, inode);
+}
+
static inline int security_task_create (unsigned long clone_flags)
{
return security_ops->task_create (clone_flags);
@@ -2579,6 +2611,19 @@ static inline void security_cred_destroy(struct cred *cred)
{
}
+static inline int security_cred_kernel_act_as(struct cred *cred,
+ const char *service,
+ struct task_struct *daemon)
+{
+ return 0;
+}
+
+static inline int security_cred_create_files_as(struct cred *cred,
+ struct inode *inode)
+{
+ return 0;
+}
+
static inline int security_task_create (unsigned long clone_flags)
{
return 0;
diff --git a/kernel/cred.c b/kernel/cred.c
index c391f66..46a6661 100644
--- a/kernel/cred.c
+++ b/kernel/cred.c
@@ -206,3 +206,71 @@ void change_cap(struct cred *cred, kernel_cap_t cap)
}
EXPORT_SYMBOL(change_cap);
+
+/**
+ * get_kernel_cred - Get credentials for a named kernel service
+ * @service: The name of the service
+ * @daemon: A userspace daemon to be used as a base for the context
+ *
+ * Get a set of credentials for a specific kernel service. These can then be
+ * used to override a task's credentials so that work can be done on behalf of
+ * that task.
+ *
+ * @daemon is used to provide a base for the security context, but can be NULL.
+ * If @deamon is supplied, then the cred's uid, gid and groups list will be
+ * derived from that; otherwise they'll be set to 0 and no groups.
+ *
+ * @daemon is also passd to the LSM module as a base from which to initialise
+ * any MAC controls.
+ *
+ * The caller may change these controls afterwards if desired.
+ */
+struct cred *get_kernel_cred(const char *service,
+ struct task_struct *daemon)
+{
+ struct cred *cred, *dcred;
+ int ret;
+
+ cred = kzalloc(sizeof *cred, GFP_KERNEL);
+ if (!cred)
+ return ERR_PTR(-ENOMEM);
+
+ if (daemon) {
+ rcu_read_lock();
+ dcred = task_cred(daemon);
+ cred->uid = dcred->uid;
+ cred->gid = dcred->gid;
+ cred->group_info = dcred->group_info;
+ atomic_inc(&cred->group_info->usage);
+ rcu_read_unlock();
+ } else {
+ cred->group_info = &init_groups;
+ atomic_inc(&init_groups.usage);
+ }
+
+ ret = security_cred_kernel_act_as(cred, service, daemon);
+ if (ret < 0) {
+ put_cred(cred);
+ return ERR_PTR(ret);
+ }
+
+ return cred;
+}
+
+EXPORT_SYMBOL(get_kernel_cred);
+
+/**
+ * change_create_files_as - Change the file creation context in a new cred record
+ * @cred: The credential record to alter
+ * @inode: The inode to take the context from
+ *
+ * Change the file creation context in a new credentials record to be the same
+ * as the object context of the specified inode, so that the new inodes have
+ * the same MAC context as that inode.
+ */
+int change_create_files_as(struct cred *cred, struct inode *inode)
+{
+ return security_cred_create_files_as(cred, inode);
+}
+
+EXPORT_SYMBOL(change_create_files_as);
diff --git a/security/dummy.c b/security/dummy.c
index f403658..004bb21 100644
--- a/security/dummy.c
+++ b/security/dummy.c
@@ -488,6 +488,17 @@ static void dummy_cred_destroy(struct cred *cred)
{
}
+static int dummy_cred_kernel_act_as(struct cred *cred, const char *service,
+ struct task_struct *daemon)
+{
+ return 0;
+}
+
+static int dummy_cred_create_files_as(struct cred *cred, struct inode *inode)
+{
+ return 0;
+}
+
static int dummy_task_create (unsigned long clone_flags)
{
return 0;
@@ -1063,6 +1074,8 @@ void security_fixup_ops (struct security_operations *ops)
set_to_dummy_if_null(ops, file_receive);
set_to_dummy_if_null(ops, cred_dup);
set_to_dummy_if_null(ops, cred_destroy);
+ set_to_dummy_if_null(ops, cred_kernel_act_as);
+ set_to_dummy_if_null(ops, cred_create_files_as);
set_to_dummy_if_null(ops, task_create);
set_to_dummy_if_null(ops, task_alloc_security);
set_to_dummy_if_null(ops, task_free_security);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 4e72dbb..fa11211 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2771,6 +2771,51 @@ static void selinux_cred_destroy(struct cred *cred)
kfree(cred->security);
}
+/*
+ * get the credentials for a kernel service, deriving the subjective context
+ * from the credentials of a userspace daemon if one supplied
+ * - all the creation contexts are set to unlabelled
+ */
+static int selinux_cred_kernel_act_as(struct cred *cred,
+ const char *service,
+ struct task_struct *daemon)
+{
+ struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
+ u32 ksid;
+ int ret;
+
+ tsec = daemon ? daemon->security : init_task.security;
+
+ ret = security_transition_sid(tsec->victim_sid, SECINITSID_KERNEL,
+ SECCLASS_PROCESS, &ksid);
+ if (ret < 0)
+ return ret;
+
+ csec = kzalloc(sizeof(struct cred_security_struct), GFP_KERNEL);
+ if (!csec)
+ return -ENOMEM;
+
+ csec->action_sid = ksid;
+ csec->create_sid = SECINITSID_UNLABELED;
+ csec->keycreate_sid = SECINITSID_UNLABELED;
+ csec->sockcreate_sid = SECINITSID_UNLABELED;
+ cred->security = csec;
+ return 0;
+}
+
+/*
+ * set the file creation context in a credentials record to the same as the
+ * objective context of the specified inode
+ */
+static int selinux_cred_create_files_as(struct cred *cred, struct inode *inode)
+{
+ struct cred_security_struct *csec = cred->security;
+ struct inode_security_struct *isec = inode->i_security;
+
+ csec->create_sid = isec->sid;
+ return 0;
+}
/* task security operations */
@@ -4887,6 +4932,8 @@ static struct security_operations selinux_ops = {
.cred_dup = selinux_cred_dup,
.cred_destroy = selinux_cred_destroy,
+ .cred_kernel_act_as = selinux_cred_kernel_act_as,
+ .cred_create_files_as = selinux_cred_create_files_as,
.task_create = selinux_task_create,
.task_alloc_security = selinux_task_alloc_security,
Recruit a couple of page flags to aid in cache management. The following extra
flags are defined:
(1) PG_fscache (PG_owner_priv_2)
The marked page is backed by a local cache and is pinning resources in the
cache driver.
(2) PG_fscache_write (PG_owner_priv_3)
The marked page is being written to the local cache. The page may not be
modified whilst this is in progress.
If PG_fscache is set, then things that checked for PG_private will now also
check for that. This includes things like truncation and page invalidation.
The function page_has_private() had been added to detect this.
Signed-off-by: David Howells <[email protected]>
---
fs/splice.c | 2 +-
include/linux/page-flags.h | 30 +++++++++++++++++++++++++++++-
include/linux/pagemap.h | 11 +++++++++++
mm/filemap.c | 16 ++++++++++++++++
mm/migrate.c | 2 +-
mm/page_alloc.c | 3 +++
mm/readahead.c | 9 +++++----
mm/swap.c | 4 ++--
mm/swap_state.c | 4 ++--
mm/truncate.c | 10 +++++-----
mm/vmscan.c | 2 +-
11 files changed, 76 insertions(+), 17 deletions(-)
diff --git a/fs/splice.c b/fs/splice.c
index ceb1f07..1a8b80c 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -58,7 +58,7 @@ static int page_cache_pipe_buf_steal(struct pipe_inode_info *pipe,
*/
wait_on_page_writeback(page);
- if (PagePrivate(page))
+ if (page_has_private(page))
try_to_release_page(page, GFP_KERNEL);
/*
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 209d3a4..eaf9854 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -83,19 +83,24 @@
#define PG_private 11 /* If pagecache, has fs-private data */
#define PG_writeback 12 /* Page is under writeback */
+#define PG_owner_priv_2 13 /* Owner use. If pagecache, fs may use */
#define PG_compound 14 /* Part of a compound page */
#define PG_swapcache 15 /* Swap page: swp_entry_t in private */
#define PG_mappedtodisk 16 /* Has blocks allocated on-disk */
#define PG_reclaim 17 /* To be reclaimed asap */
+#define PG_owner_priv_3 18 /* Owner use. If pagecache, fs may use */
#define PG_buddy 19 /* Page is free, on buddy lists */
/* PG_readahead is only used for file reads; PG_reclaim is only for writes */
#define PG_readahead PG_reclaim /* Reminder to do async read-ahead */
-/* PG_owner_priv_1 users should have descriptive aliases */
+/* PG_owner_priv_1/2/3 users should have descriptive aliases */
#define PG_checked PG_owner_priv_1 /* Used by some filesystems */
#define PG_pinned PG_owner_priv_1 /* Xen pinned pagetable */
+#define PG_fscache PG_owner_priv_2 /* Backed by local cache */
+#define PG_fscache_write PG_owner_priv_3 /* Writing to local cache */
+
#if (BITS_PER_LONG > 32)
/*
@@ -199,6 +204,18 @@ static inline void SetPageUptodate(struct page *page)
#define TestClearPageWriteback(page) test_and_clear_bit(PG_writeback, \
&(page)->flags)
+#define PageFsCache(page) test_bit(PG_fscache, &(page)->flags)
+#define SetPageFsCache(page) set_bit(PG_fscache, &(page)->flags)
+#define ClearPageFsCache(page) clear_bit(PG_fscache, &(page)->flags)
+#define TestSetPageFsCache(page) test_and_set_bit(PG_fscache, &(page)->flags)
+#define TestClearPageFsCache(page) test_and_clear_bit(PG_fscache, &(page)->flags)
+
+#define PageFsCacheWrite(page) test_bit(PG_fscache_write, &(page)->flags)
+#define SetPageFsCacheWrite(page) set_bit(PG_fscache_write, &(page)->flags)
+#define ClearPageFsCacheWrite(page) clear_bit(PG_fscache_write, &(page)->flags)
+#define TestSetPageFsCacheWrite(page) test_and_set_bit(PG_fscache_write, &(page)->flags)
+#define TestClearPageFsCacheWrite(page) test_and_clear_bit(PG_fscache_write, &(page)->flags)
+
#define PageBuddy(page) test_bit(PG_buddy, &(page)->flags)
#define __SetPageBuddy(page) __set_bit(PG_buddy, &(page)->flags)
#define __ClearPageBuddy(page) __clear_bit(PG_buddy, &(page)->flags)
@@ -272,4 +289,15 @@ static inline void set_page_writeback(struct page *page)
test_set_page_writeback(page);
}
+/**
+ * page_has_private - Determine if page has private stuff
+ * @page: The page to be checked
+ *
+ * Determine if a page has private stuff, indicating that release routines
+ * should be invoked upon it.
+ */
+#define page_has_private(page) \
+ ((page)->flags & ((1 << PG_private) | \
+ (1 << PG_fscache)))
+
#endif /* PAGE_FLAGS_H */
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 8a83537..d1049b6 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -208,6 +208,17 @@ static inline void wait_on_page_writeback(struct page *page)
extern void end_page_writeback(struct page *page);
+/*
+ * Wait for a page to finish being written to a local cache
+ */
+static inline void wait_on_page_fscache_write(struct page *page)
+{
+ if (PageFsCacheWrite(page))
+ wait_on_page_bit(page, PG_fscache_write);
+}
+
+extern void end_page_fscache_write(struct page *page);
+
/*
* Fault a userspace page into pagetables. Return non-zero on a fault.
*
diff --git a/mm/filemap.c b/mm/filemap.c
index 3257be2..3a61923 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -557,6 +557,19 @@ void end_page_writeback(struct page *page)
EXPORT_SYMBOL(end_page_writeback);
/**
+ * end_page_fscache_write - End writing page to cache
+ * @page: the page
+ */
+void end_page_fscache_write(struct page *page)
+{
+ if (!TestClearPageFsCacheWrite(page))
+ BUG();
+ smp_mb__after_clear_bit();
+ wake_up_page(page, PG_fscache_write);
+}
+EXPORT_SYMBOL(end_page_fscache_write);
+
+/**
* __lock_page - get a lock on the page, assuming we need to sleep to get it
* @page: the page to lock
*
@@ -2227,6 +2240,9 @@ out:
* (presumably at page->private). If the release was successful, return `1'.
* Otherwise return zero.
*
+ * This may also be called if PG_fscache is set on a page, indicating that the
+ * page is known to the local caching routines.
+ *
* The @gfp_mask argument specifies whether I/O may be performed to release
* this page (__GFP_IO), and whether the call may block (__GFP_WAIT).
*
diff --git a/mm/migrate.c b/mm/migrate.c
index 79a1909..8e3669e 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -544,7 +544,7 @@ static int fallback_migrate_page(struct address_space *mapping,
* Buffers may be managed in a filesystem specific way.
* We must have no buffers or drop them.
*/
- if (PagePrivate(page) &&
+ if (page_has_private(page) &&
!try_to_release_page(page, GFP_KERNEL))
return -EAGAIN;
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 1a8c595..1bc73b7 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -208,6 +208,7 @@ static void bad_page(struct page *page)
dump_stack();
page->flags &= ~(1 << PG_lru |
1 << PG_private |
+ 1 << PG_fscache |
1 << PG_locked |
1 << PG_active |
1 << PG_dirty |
@@ -445,6 +446,7 @@ static inline int free_pages_check(struct page *page)
(page->flags & (
1 << PG_lru |
1 << PG_private |
+ 1 << PG_fscache |
1 << PG_locked |
1 << PG_active |
1 << PG_slab |
@@ -593,6 +595,7 @@ static int prep_new_page(struct page *page, int order, gfp_t gfp_flags)
(page->flags & (
1 << PG_lru |
1 << PG_private |
+ 1 << PG_fscache |
1 << PG_locked |
1 << PG_active |
1 << PG_dirty |
diff --git a/mm/readahead.c b/mm/readahead.c
index 12d1378..8b5e017 100644
--- a/mm/readahead.c
+++ b/mm/readahead.c
@@ -54,14 +54,15 @@ EXPORT_SYMBOL_GPL(file_ra_state_init);
/*
* see if a page needs releasing upon read_cache_pages() failure
- * - the caller of read_cache_pages() may have set PG_private before calling,
- * such as the NFS fs marking pages that are cached locally on disk, thus we
- * need to give the fs a chance to clean up in the event of an error
+ * - the caller of read_cache_pages() may have set PG_private or PG_fscache
+ * before calling, such as the NFS fs marking pages that are cached locally
+ * on disk, thus we need to give the fs a chance to clean up in the event of
+ * an error
*/
static void read_cache_pages_invalidate_page(struct address_space *mapping,
struct page *page)
{
- if (PagePrivate(page)) {
+ if (page_has_private(page)) {
if (TestSetPageLocked(page))
BUG();
page->mapping = mapping;
diff --git a/mm/swap.c b/mm/swap.c
index d3cb966..498d0c1 100644
--- a/mm/swap.c
+++ b/mm/swap.c
@@ -412,8 +412,8 @@ void pagevec_strip(struct pagevec *pvec)
for (i = 0; i < pagevec_count(pvec); i++) {
struct page *page = pvec->pages[i];
- if (PagePrivate(page) && !TestSetPageLocked(page)) {
- if (PagePrivate(page))
+ if (page_has_private(page) && !TestSetPageLocked(page)) {
+ if (page_has_private(page))
try_to_release_page(page, 0);
unlock_page(page);
}
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 67daecb..93667ef 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -75,7 +75,7 @@ static int __add_to_swap_cache(struct page *page, swp_entry_t entry,
int error;
BUG_ON(PageSwapCache(page));
- BUG_ON(PagePrivate(page));
+ BUG_ON(page_has_private(page));
error = radix_tree_preload(gfp_mask);
if (!error) {
write_lock_irq(&swapper_space.tree_lock);
@@ -126,7 +126,7 @@ void __delete_from_swap_cache(struct page *page)
BUG_ON(!PageLocked(page));
BUG_ON(!PageSwapCache(page));
BUG_ON(PageWriteback(page));
- BUG_ON(PagePrivate(page));
+ BUG_ON(page_has_private(page));
radix_tree_delete(&swapper_space.page_tree, page_private(page));
set_page_private(page, 0);
diff --git a/mm/truncate.c b/mm/truncate.c
index 5cdfbc1..5555cb0 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -48,7 +48,7 @@ void do_invalidatepage(struct page *page, unsigned long offset)
static inline void truncate_partial_page(struct page *page, unsigned partial)
{
zero_user_page(page, partial, PAGE_CACHE_SIZE - partial, KM_USER0);
- if (PagePrivate(page))
+ if (page_has_private(page))
do_invalidatepage(page, partial);
}
@@ -97,7 +97,7 @@ truncate_complete_page(struct address_space *mapping, struct page *page)
cancel_dirty_page(page, PAGE_CACHE_SIZE);
- if (PagePrivate(page))
+ if (page_has_private(page))
do_invalidatepage(page, 0);
remove_from_page_cache(page);
@@ -122,7 +122,7 @@ invalidate_complete_page(struct address_space *mapping, struct page *page)
if (page->mapping != mapping)
return 0;
- if (PagePrivate(page) && !try_to_release_page(page, 0))
+ if (page_has_private(page) && !try_to_release_page(page, 0))
return 0;
ret = remove_mapping(mapping, page);
@@ -344,14 +344,14 @@ invalidate_complete_page2(struct address_space *mapping, struct page *page)
if (page->mapping != mapping)
return 0;
- if (PagePrivate(page) && !try_to_release_page(page, GFP_KERNEL))
+ if (page_has_private(page) && !try_to_release_page(page, GFP_KERNEL))
return 0;
write_lock_irq(&mapping->tree_lock);
if (PageDirty(page))
goto failed;
- BUG_ON(PagePrivate(page));
+ BUG_ON(page_has_private(page));
__remove_from_page_cache(page);
write_unlock_irq(&mapping->tree_lock);
ClearPageUptodate(page);
diff --git a/mm/vmscan.c b/mm/vmscan.c
index a6e65d0..c601afa 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -578,7 +578,7 @@ static unsigned long shrink_page_list(struct list_head *page_list,
* process address space (page_count == 1) it can be freed.
* Otherwise, leave the page on the LRU so it is swappable.
*/
- if (PagePrivate(page)) {
+ if (page_has_private(page)) {
if (!try_to_release_page(page, sc->gfp_mask))
goto activate_locked;
if (!mapping && page_count(page) == 1)
Move the effective capabilities mask from the task struct into the credentials
record.
Note that the effective capabilities mask in the cred struct shadows that in
the task_struct because a thread can have its capabilities masks changed by
another thread. The shadowing is performed by update_current_cred() which is
invoked on entry to any system call that might need it.
Signed-off-by: David Howells <[email protected]>
---
fs/buffer.c | 3 +++
fs/ioprio.c | 3 +++
fs/open.c | 27 +++++++++------------------
fs/proc/array.c | 2 +-
fs/readdir.c | 3 +++
include/linux/cred.h | 2 ++
include/linux/init_task.h | 2 +-
include/linux/sched.h | 2 +-
ipc/msg.c | 3 +++
ipc/sem.c | 3 +++
ipc/shm.c | 3 +++
kernel/acct.c | 3 +++
kernel/capability.c | 3 +++
kernel/compat.c | 3 +++
kernel/cred.c | 36 +++++++++++++++++++++++++++++-------
kernel/exit.c | 2 ++
kernel/fork.c | 6 +++++-
kernel/futex.c | 3 +++
kernel/futex_compat.c | 3 +++
kernel/kexec.c | 3 +++
kernel/module.c | 6 ++++++
kernel/ptrace.c | 3 +++
kernel/sched.c | 9 +++++++++
kernel/signal.c | 6 ++++++
kernel/sys.c | 39 +++++++++++++++++++++++++++++++++++++++
kernel/sysctl.c | 3 +++
kernel/time.c | 9 +++++++++
kernel/uid16.c | 3 +++
mm/mempolicy.c | 6 ++++++
mm/migrate.c | 3 +++
mm/mlock.c | 4 ++++
mm/mmap.c | 3 +++
mm/mremap.c | 3 +++
mm/oom_kill.c | 9 +++++++--
mm/swapfile.c | 6 ++++++
net/compat.c | 6 ++++++
net/socket.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
security/dummy.c | 22 ++++++++++++++++++----
38 files changed, 265 insertions(+), 35 deletions(-)
diff --git a/fs/buffer.c b/fs/buffer.c
index 0e5ec37..9aabf79 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -2909,6 +2909,9 @@ asmlinkage long sys_bdflush(int func, long data)
{
static int msg_count;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
diff --git a/fs/ioprio.c b/fs/ioprio.c
index 10d2c21..d32b7b7 100644
--- a/fs/ioprio.c
+++ b/fs/ioprio.c
@@ -63,6 +63,9 @@ asmlinkage long sys_ioprio_set(int which, int who, int ioprio)
struct pid *pgrp;
int ret;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
switch (class) {
case IOPRIO_CLASS_RT:
if (!capable(CAP_SYS_ADMIN))
diff --git a/fs/open.c b/fs/open.c
index 0c05863..f765ec5 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -450,7 +450,7 @@ out:
asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode)
{
struct nameidata nd;
- kernel_cap_t old_cap;
+ kernel_cap_t old_cap, want_cap = CAP_EMPTY_SET;
struct cred *cred;
int res;
@@ -461,33 +461,26 @@ asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode)
if (res < 0)
return res;
- old_cap = current->cap_effective;
+ /* Clear the capabilities if we switch to a non-root user */
+ if (!current->uid)
+ want_cap = current->cap_permitted;
+
+ old_cap = current->cred->cap_effective;
if (current->cred->uid != current->uid ||
- current->cred->gid != current->gid) {
+ current->cred->gid != current->gid ||
+ current->cred->cap_effective != want_cap) {
cred = dup_cred(current->cred);
if (!cred)
return -ENOMEM;
change_fsuid(cred, current->uid);
change_fsgid(cred, current->gid);
+ change_cap(cred, want_cap);
} else {
cred = get_current_cred();
}
- /*
- * Clear the capabilities if we switch to a non-root user
- *
- * FIXME: There is a race here against sys_capset. The
- * capabilities can change yet we will restore the old
- * value below. We should hold task_capabilities_lock,
- * but we cannot because user_path_walk can sleep.
- */
- if (current->uid)
- cap_clear(current->cap_effective);
- else
- current->cap_effective = current->cap_permitted;
-
cred = __set_current_cred(cred);
res = __user_walk_fd(dfd, filename, LOOKUP_FOLLOW|LOOKUP_ACCESS, &nd);
if (res)
@@ -506,8 +499,6 @@ out_path_release:
path_release(&nd);
out:
set_current_cred(cred);
- current->cap_effective = old_cap;
-
return res;
}
diff --git a/fs/proc/array.c b/fs/proc/array.c
index dc2f83a..1a406c7 100644
--- a/fs/proc/array.c
+++ b/fs/proc/array.c
@@ -286,7 +286,7 @@ static inline char *task_cap(struct task_struct *p, char *buffer)
"CapEff:\t%016x\n",
cap_t(p->cap_inheritable),
cap_t(p->cap_permitted),
- cap_t(p->cap_effective));
+ cap_t(p->_cap_effective));
}
static inline char *task_context_switch_counts(struct task_struct *p,
diff --git a/fs/readdir.c b/fs/readdir.c
index 57e6aa9..33c69ac 100644
--- a/fs/readdir.c
+++ b/fs/readdir.c
@@ -103,6 +103,9 @@ asmlinkage long old_readdir(unsigned int fd, struct old_linux_dirent __user * di
struct file * file;
struct readdir_callback buf;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
error = -EBADF;
file = fget(fd);
if (!file)
diff --git a/include/linux/cred.h b/include/linux/cred.h
index 98d5279..f2df1c3 100644
--- a/include/linux/cred.h
+++ b/include/linux/cred.h
@@ -24,6 +24,7 @@ struct cred {
atomic_t usage;
uid_t uid; /* fsuid as was */
gid_t gid; /* fsgid as was */
+ kernel_cap_t cap_effective;
struct rcu_head exterminate; /* cred destroyer */
struct group_info *group_info;
void *security;
@@ -46,6 +47,7 @@ extern void put_cred(struct cred *);
extern void change_fsuid(struct cred *, uid_t);
extern void change_fsgid(struct cred *, gid_t);
extern void change_groups(struct cred *, struct group_info *);
+extern void change_cap(struct cred *, kernel_cap_t);
extern struct cred *dup_cred(const struct cred *);
/**
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index 5cb7931..56d4be3 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -141,7 +141,7 @@ extern struct nsproxy init_nsproxy;
.sibling = LIST_HEAD_INIT(tsk.sibling), \
.group_leader = &tsk, \
.cred = &init_cred, \
- .cap_effective = CAP_INIT_EFF_SET, \
+ ._cap_effective = CAP_INIT_EFF_SET, \
.cap_inheritable = CAP_INIT_INH_SET, \
.cap_permitted = CAP_FULL_SET, \
.keep_capabilities = 0, \
diff --git a/include/linux/sched.h b/include/linux/sched.h
index ca0d553..52f2b64 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1037,7 +1037,7 @@ struct task_struct {
struct cred *cred;
uid_t uid,euid,suid;
gid_t gid,egid,sgid;
- kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
+ kernel_cap_t _cap_effective, cap_inheritable, cap_permitted;
unsigned keep_capabilities:1;
struct user_struct *user;
#ifdef CONFIG_KEYS
diff --git a/ipc/msg.c b/ipc/msg.c
index a03fcb5..a351c89 100644
--- a/ipc/msg.c
+++ b/ipc/msg.c
@@ -393,6 +393,9 @@ asmlinkage long sys_msgctl(int msqid, int cmd, struct msqid_ds __user *buf)
if (msqid < 0 || cmd < 0)
return -EINVAL;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
version = ipc_parse_version(&cmd);
ns = current->nsproxy->ipc_ns;
diff --git a/ipc/sem.c b/ipc/sem.c
index b676fef..9691b40 100644
--- a/ipc/sem.c
+++ b/ipc/sem.c
@@ -927,6 +927,9 @@ asmlinkage long sys_semctl (int semid, int semnum, int cmd, union semun arg)
if (semid < 0)
return -EINVAL;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
version = ipc_parse_version(&cmd);
ns = current->nsproxy->ipc_ns;
diff --git a/ipc/shm.c b/ipc/shm.c
index a86a3a5..709a4fe 100644
--- a/ipc/shm.c
+++ b/ipc/shm.c
@@ -589,6 +589,9 @@ asmlinkage long sys_shmctl (int shmid, int cmd, struct shmid_ds __user *buf)
goto out;
}
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
version = ipc_parse_version(&cmd);
ns = current->nsproxy->ipc_ns;
diff --git a/kernel/acct.c b/kernel/acct.c
index 24f0f8b..01961a5 100644
--- a/kernel/acct.c
+++ b/kernel/acct.c
@@ -253,6 +253,9 @@ asmlinkage long sys_acct(const char __user *name)
{
int error;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!capable(CAP_SYS_PACCT))
return -EPERM;
diff --git a/kernel/capability.c b/kernel/capability.c
index c8d3c77..3ae73f9 100644
--- a/kernel/capability.c
+++ b/kernel/capability.c
@@ -178,6 +178,9 @@ asmlinkage long sys_capset(cap_user_header_t header, const cap_user_data_t data)
int ret;
pid_t pid;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (get_user(version, &header->version))
return -EFAULT;
diff --git a/kernel/compat.c b/kernel/compat.c
index 3bae374..04be932 100644
--- a/kernel/compat.c
+++ b/kernel/compat.c
@@ -909,6 +909,9 @@ asmlinkage long compat_sys_adjtimex(struct compat_timex __user *utp)
struct timex txc;
int ret;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
memset(&txc, 0, sizeof(struct timex));
if (!access_ok(VERIFY_READ, utp, sizeof(struct compat_timex)) ||
diff --git a/kernel/cred.c b/kernel/cred.c
index 5b827cb..c391f66 100644
--- a/kernel/cred.c
+++ b/kernel/cred.c
@@ -21,16 +21,19 @@
*/
struct cred init_cred = {
.usage = ATOMIC_INIT(2),
+ .cap_effective = CAP_INIT_EFF_SET,
.group_info = &init_groups,
};
/**
* update_current_cred - Bring the current task's creds up to date
*
- * Bring the current task's credentials up to date with respect to the keyrings
- * they shadow. The process and session level keyrings may get changed by
- * sibling threads with the same process, but the change can't be applied back
- * to this thread's cred struct except by this thread itself.
+ * Bring the current task's credential record up to date with respect to the
+ * effective capability mask and keyrings it shadows. The capabilities mask
+ * may get changed by other processes, and process and session level keyrings
+ * may get changed by sibling threads with the same process, but the change
+ * can't be applied back to this thread's cred struct except by this thread
+ * itself.
*/
int update_current_cred(void)
{
@@ -44,16 +47,21 @@ int update_current_cred(void)
key_ref_to_ptr(cred->process_keyring) == sig->process_keyring &&
key_ref_to_ptr(cred->thread_keyring) == current->thread_keyring &&
#endif
- true)
+ cred->cap_effective != current->_cap_effective)
return 0;
- cred = kmalloc(sizeof(struct cred), GFP_KERNEL);
+ cred = kmemdup(current->cred, sizeof(struct cred), GFP_KERNEL);
if (!cred)
return -ENOMEM;
- *cred = *current->cred;
+ if (security_cred_dup(cred) < 0) {
+ kfree(cred);
+ return -ENOMEM;
+ }
+
atomic_set(&cred->usage, 1);
get_group_info(cred->group_info);
+ cred->cap_effective = current->_cap_effective;
#ifdef CONFIG_KEYS
rcu_read_lock();
@@ -184,3 +192,17 @@ void change_groups(struct cred *cred, struct group_info *group_info)
}
EXPORT_SYMBOL(change_groups);
+
+/**
+ * change_cap - Change the supplementary groups in a new credential record
+ * @cred: The credential record to alter
+ * @cap: The capabilities to set
+ *
+ * Change the effective capabilities in a new credential record.
+ */
+void change_cap(struct cred *cred, kernel_cap_t cap)
+{
+ cred->cap_effective = cap;
+}
+
+EXPORT_SYMBOL(change_cap);
diff --git a/kernel/exit.c b/kernel/exit.c
index c366ae7..a9916e5 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -888,6 +888,8 @@ fastcall NORET_TYPE void do_exit(long code)
struct task_struct *tsk = current;
int group_dead;
+ update_current_cred();
+
profile_task_exit(tsk);
WARN_ON(atomic_read(&tsk->fs_excl));
diff --git a/kernel/fork.c b/kernel/fork.c
index 677c353..e2948ed 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1422,9 +1422,13 @@ long do_fork(unsigned long clone_flags,
{
struct task_struct *p;
int trace = 0;
- struct pid *pid = alloc_pid();
+ struct pid *pid;
long nr;
+ if (update_current_cred())
+ return -ENOMEM;
+
+ pid = alloc_pid();
if (!pid)
return -EAGAIN;
nr = pid->nr;
diff --git a/kernel/futex.c b/kernel/futex.c
index e8935b1..40070fe 100644
--- a/kernel/futex.c
+++ b/kernel/futex.c
@@ -1846,6 +1846,9 @@ sys_get_robust_list(int pid, struct robust_list_head __user * __user *head_ptr,
struct robust_list_head __user *head;
unsigned long ret;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!pid)
head = current->robust_list;
else {
diff --git a/kernel/futex_compat.c b/kernel/futex_compat.c
index 7e52eb0..a872029 100644
--- a/kernel/futex_compat.c
+++ b/kernel/futex_compat.c
@@ -109,6 +109,9 @@ compat_sys_get_robust_list(int pid, compat_uptr_t __user *head_ptr,
struct compat_robust_list_head __user *head;
unsigned long ret;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!pid)
head = current->compat_robust_list;
else {
diff --git a/kernel/kexec.c b/kernel/kexec.c
index 25db14b..e1feb2f 100644
--- a/kernel/kexec.c
+++ b/kernel/kexec.c
@@ -921,6 +921,9 @@ asmlinkage long sys_kexec_load(unsigned long entry, unsigned long nr_segments,
int locked;
int result;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
/* We only trust the superuser with rebooting the system. */
if (!capable(CAP_SYS_BOOT))
return -EPERM;
diff --git a/kernel/module.c b/kernel/module.c
index db0ead0..32893a5 100644
--- a/kernel/module.c
+++ b/kernel/module.c
@@ -660,6 +660,9 @@ sys_delete_module(const char __user *name_user, unsigned int flags)
char name[MODULE_NAME_LEN];
int ret, forced = 0;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!capable(CAP_SYS_MODULE))
return -EPERM;
@@ -1978,6 +1981,9 @@ sys_init_module(void __user *umod,
struct module *mod;
int ret = 0;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
/* Must have permission */
if (!capable(CAP_SYS_MODULE))
return -EPERM;
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index 3eca7a5..15fb1ff 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -456,6 +456,9 @@ asmlinkage long sys_ptrace(long request, long pid, long addr, long data)
struct task_struct *child;
long ret;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
/*
* This lock_kernel fixes a subtle race with suid exec
*/
diff --git a/kernel/sched.c b/kernel/sched.c
index 6107a0c..602f526 100644
--- a/kernel/sched.c
+++ b/kernel/sched.c
@@ -4063,6 +4063,9 @@ asmlinkage long sys_nice(int increment)
{
long nice, retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
/*
* Setpriority might change our priority at the same moment.
* We don't have to worry. Conceptually one call occurs first
@@ -4295,6 +4298,9 @@ do_sched_setscheduler(pid_t pid, int policy, struct sched_param __user *param)
struct task_struct *p;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!param || pid < 0)
return -EINVAL;
if (copy_from_user(&lparam, param, sizeof(struct sched_param)))
@@ -4468,6 +4474,9 @@ asmlinkage long sys_sched_setaffinity(pid_t pid, unsigned int len,
cpumask_t new_mask;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
retval = get_user_cpu_mask(user_mask_ptr, len, &new_mask);
if (retval)
return retval;
diff --git a/kernel/signal.c b/kernel/signal.c
index 9fb91a3..0a3358f 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -2197,6 +2197,9 @@ sys_kill(int pid, int sig)
{
struct siginfo info;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
info.si_signo = sig;
info.si_errno = 0;
info.si_code = SI_USER;
@@ -2212,6 +2215,9 @@ static int do_tkill(int tgid, int pid, int sig)
struct siginfo info;
struct task_struct *p;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
error = -ESRCH;
info.si_signo = sig;
info.si_errno = 0;
diff --git a/kernel/sys.c b/kernel/sys.c
index 9bb591f..ff34679 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -670,6 +670,9 @@ asmlinkage long sys_setpriority(int which, int who, int niceval)
int error = -EINVAL;
struct pid *pgrp;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (which > PRIO_USER || which < PRIO_PROCESS)
goto out;
@@ -896,6 +899,9 @@ asmlinkage long sys_reboot(int magic1, int magic2, unsigned int cmd, void __user
{
char buffer[256];
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
/* We only trust the superuser with rebooting the system. */
if (!capable(CAP_SYS_BOOT))
return -EPERM;
@@ -1019,6 +1025,9 @@ asmlinkage long sys_setregid(gid_t rgid, gid_t egid)
int new_egid = old_egid;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
retval = security_task_setgid(rgid, egid, (gid_t)-1, LSM_SETID_RE);
if (retval)
return retval;
@@ -1072,6 +1081,9 @@ asmlinkage long sys_setgid(gid_t gid)
int old_egid = current->egid;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
retval = security_task_setgid(gid, (gid_t)-1, (gid_t)-1, LSM_SETID_ID);
if (retval)
return retval;
@@ -1150,6 +1162,9 @@ asmlinkage long sys_setreuid(uid_t ruid, uid_t euid)
int old_ruid, old_euid, old_suid, new_ruid, new_euid;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
retval = security_task_setuid(ruid, euid, (uid_t)-1, LSM_SETID_RE);
if (retval)
return retval;
@@ -1221,6 +1236,9 @@ asmlinkage long sys_setuid(uid_t uid)
int old_ruid, old_suid, new_suid;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
retval = security_task_setuid(uid, (uid_t)-1, (uid_t)-1, LSM_SETID_ID);
if (retval)
return retval;
@@ -1271,6 +1289,9 @@ asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
int old_suid = current->suid;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
retval = security_task_setuid(ruid, euid, suid, LSM_SETID_RES);
if (retval)
return retval;
@@ -1333,6 +1354,9 @@ asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
struct cred *cred;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
retval = security_task_setgid(rgid, egid, sgid, LSM_SETID_RES);
if (retval)
return retval;
@@ -1876,6 +1900,9 @@ asmlinkage long sys_setgroups(int gidsetsize, gid_t __user *grouplist)
struct group_info *group_info;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!capable(CAP_SETGID))
return -EPERM;
if ((unsigned)gidsetsize > NGROUPS_MAX)
@@ -1941,6 +1968,9 @@ asmlinkage long sys_sethostname(char __user *name, int len)
int errno;
char tmp[__NEW_UTS_LEN];
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (len < 0 || len > __NEW_UTS_LEN)
@@ -1986,6 +2016,9 @@ asmlinkage long sys_setdomainname(char __user *name, int len)
int errno;
char tmp[__NEW_UTS_LEN];
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (len < 0 || len > __NEW_UTS_LEN)
@@ -2045,6 +2078,9 @@ asmlinkage long sys_setrlimit(unsigned int resource, struct rlimit __user *rlim)
unsigned long it_prof_secs;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (resource >= RLIM_NLIMITS)
return -EINVAL;
if (copy_from_user(&new_rlim, rlim, sizeof(*rlim)))
@@ -2226,6 +2262,9 @@ asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3,
{
long error;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
error = security_task_prctl(option, arg2, arg3, arg4, arg5);
if (error)
return error;
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 53a456e..9447293 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1347,6 +1347,9 @@ asmlinkage long sys_sysctl(struct __sysctl_args __user *args)
struct __sysctl_args tmp;
int error;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (copy_from_user(&tmp, args, sizeof(tmp)))
return -EFAULT;
diff --git a/kernel/time.c b/kernel/time.c
index 2289a8d..975f47d 100644
--- a/kernel/time.c
+++ b/kernel/time.c
@@ -82,6 +82,9 @@ asmlinkage long sys_stime(time_t __user *tptr)
struct timespec tv;
int err;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (get_user(tv.tv_sec, tptr))
return -EFAULT;
@@ -186,6 +189,9 @@ asmlinkage long sys_settimeofday(struct timeval __user *tv,
struct timespec new_ts;
struct timezone new_tz;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (tv) {
if (copy_from_user(&user_tv, tv, sizeof(*tv)))
return -EFAULT;
@@ -205,6 +211,9 @@ asmlinkage long sys_adjtimex(struct timex __user *txc_p)
struct timex txc; /* Local copy of parameter */
int ret;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
/* Copy the user data space into the kernel copy
* structure. But bear in mind that the structures
* may change
diff --git a/kernel/uid16.c b/kernel/uid16.c
index 5a8b95e..5238a96 100644
--- a/kernel/uid16.c
+++ b/kernel/uid16.c
@@ -187,6 +187,9 @@ asmlinkage long sys_setgroups16(int gidsetsize, old_gid_t __user *grouplist)
struct group_info *group_info;
int retval;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!capable(CAP_SETGID))
return -EPERM;
if ((unsigned)gidsetsize > NGROUPS_MAX)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 3d6ac95..64cfcf2 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -878,6 +878,9 @@ asmlinkage long sys_mbind(unsigned long start, unsigned long len,
nodemask_t nodes;
int err;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
err = get_nodes(&nodes, nmask, maxnode);
if (err)
return err;
@@ -914,6 +917,9 @@ asmlinkage long sys_migrate_pages(pid_t pid, unsigned long maxnode,
nodemask_t task_nodes;
int err;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
err = get_nodes(&old, old_nodes, maxnode);
if (err)
return err;
diff --git a/mm/migrate.c b/mm/migrate.c
index e2fdbce..79a1909 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -915,6 +915,9 @@ asmlinkage long sys_move_pages(pid_t pid, unsigned long nr_pages,
struct mm_struct *mm;
struct page_to_node *pm = NULL;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
/* Check flags */
if (flags & ~(MPOL_MF_MOVE|MPOL_MF_MOVE_ALL))
return -EINVAL;
diff --git a/mm/mlock.c b/mm/mlock.c
index 7b26560..67985f4 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -138,6 +138,8 @@ asmlinkage long sys_mlock(unsigned long start, size_t len)
unsigned long lock_limit;
int error = -ENOMEM;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
if (!can_do_mlock())
return -EPERM;
@@ -203,6 +205,8 @@ asmlinkage long sys_mlockall(int flags)
if (!flags || (flags & ~(MCL_CURRENT | MCL_FUTURE)))
goto out;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
ret = -EPERM;
if (!can_do_mlock())
goto out;
diff --git a/mm/mmap.c b/mm/mmap.c
index 0d40e66..1b7b0ff 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -240,6 +240,9 @@ asmlinkage unsigned long sys_brk(unsigned long brk)
unsigned long newbrk, oldbrk;
struct mm_struct *mm = current->mm;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
down_write(&mm->mmap_sem);
if (brk < mm->end_code)
diff --git a/mm/mremap.c b/mm/mremap.c
index 8ea5c24..0d49048 100644
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -418,6 +418,9 @@ asmlinkage unsigned long sys_mremap(unsigned long addr,
{
unsigned long ret;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
down_write(¤t->mm->mmap_sem);
ret = do_mremap(addr, old_len, new_len, flags, new_addr);
up_write(¤t->mm->mmap_sem);
diff --git a/mm/oom_kill.c b/mm/oom_kill.c
index f9b82ad..df5edda 100644
--- a/mm/oom_kill.c
+++ b/mm/oom_kill.c
@@ -53,6 +53,7 @@ unsigned long badness(struct task_struct *p, unsigned long uptime)
unsigned long points, cpu_time, run_time, s;
struct mm_struct *mm;
struct task_struct *child;
+ kernel_cap_t cap_effective;
task_lock(p);
mm = p->mm;
@@ -123,7 +124,11 @@ unsigned long badness(struct task_struct *p, unsigned long uptime)
* Superuser processes are usually more important, so we make it
* less likely that we kill those.
*/
- if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_ADMIN) ||
+ rcu_read_lock();
+ cap_effective = task_cred(p)->cap_effective;
+ rcu_read_unlock();
+
+ if (cap_t(cap_effective) & CAP_TO_MASK(CAP_SYS_ADMIN) ||
p->uid == 0 || p->euid == 0)
points /= 4;
@@ -133,7 +138,7 @@ unsigned long badness(struct task_struct *p, unsigned long uptime)
* tend to only have this flag set on applications they think
* of as important.
*/
- if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_RAWIO))
+ if (cap_t(cap_effective) & CAP_TO_MASK(CAP_SYS_RAWIO))
points /= 4;
/*
diff --git a/mm/swapfile.c b/mm/swapfile.c
index f071648..9539da4 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1183,6 +1183,9 @@ asmlinkage long sys_swapoff(const char __user * specialfile)
int i, type, prev;
int err;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
@@ -1433,6 +1436,9 @@ asmlinkage long sys_swapon(const char __user * specialfile, int swap_flags)
struct inode *inode = NULL;
int did_down = 0;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
spin_lock(&swap_lock);
diff --git a/net/compat.c b/net/compat.c
index d74d821..c20f404 100644
--- a/net/compat.c
+++ b/net/compat.c
@@ -483,6 +483,9 @@ asmlinkage long compat_sys_setsockopt(int fd, int level, int optname,
int err;
struct socket *sock;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (level == SOL_IPV6 && optname == IPT_SO_SET_REPLACE)
return do_netfilter_replace(fd, level, optname,
optval, optlen);
@@ -603,6 +606,9 @@ asmlinkage long compat_sys_getsockopt(int fd, int level, int optname,
int err;
struct socket *sock;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if ((sock = sockfd_lookup(fd, &err))!=NULL)
{
err = security_socket_getsockopt(sock, level,
diff --git a/net/socket.c b/net/socket.c
index 50bfeef..034d221 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -1200,6 +1200,9 @@ asmlinkage long sys_socket(int family, int type, int protocol)
int retval;
struct socket *sock;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
@@ -1228,6 +1231,9 @@ asmlinkage long sys_socketpair(int family, int type, int protocol,
int fd1, fd2, err;
struct file *newfile1, *newfile2;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
/*
* Obtain the first socket and check if the underlying protocol
* supports the socketpair call.
@@ -1323,6 +1329,9 @@ asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
char address[MAX_SOCK_ADDR];
int err, fput_needed;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
err = move_addr_to_kernel(umyaddr, addrlen, address);
@@ -1353,6 +1362,9 @@ asmlinkage long sys_listen(int fd, int backlog)
struct socket *sock;
int err, fput_needed;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
if ((unsigned)backlog > sysctl_somaxconn)
@@ -1387,6 +1399,9 @@ asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr,
int err, len, newfd, fput_needed;
char address[MAX_SOCK_ADDR];
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
@@ -1476,6 +1491,9 @@ asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr,
char address[MAX_SOCK_ADDR];
int err, fput_needed;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
@@ -1508,6 +1526,9 @@ asmlinkage long sys_getsockname(int fd, struct sockaddr __user *usockaddr,
char address[MAX_SOCK_ADDR];
int len, err, fput_needed;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
@@ -1539,6 +1560,9 @@ asmlinkage long sys_getpeername(int fd, struct sockaddr __user *usockaddr,
char address[MAX_SOCK_ADDR];
int len, err, fput_needed;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock != NULL) {
err = security_socket_getpeername(sock);
@@ -1576,6 +1600,9 @@ asmlinkage long sys_sendto(int fd, void __user *buff, size_t len,
int fput_needed;
struct file *sock_file;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock_file = fget_light(fd, &fput_needed);
err = -EBADF;
if (!sock_file)
@@ -1637,6 +1664,9 @@ asmlinkage long sys_recvfrom(int fd, void __user *ubuf, size_t size,
struct file *sock_file;
int fput_needed;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock_file = fget_light(fd, &fput_needed);
err = -EBADF;
if (!sock_file)
@@ -1693,6 +1723,9 @@ asmlinkage long sys_setsockopt(int fd, int level, int optname,
if (optlen < 0)
return -EINVAL;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock != NULL) {
err = security_socket_setsockopt(sock, level, optname);
@@ -1724,6 +1757,9 @@ asmlinkage long sys_getsockopt(int fd, int level, int optname,
int err, fput_needed;
struct socket *sock;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock != NULL) {
err = security_socket_getsockopt(sock, level, optname);
@@ -1753,6 +1789,9 @@ asmlinkage long sys_shutdown(int fd, int how)
int err, fput_needed;
struct socket *sock;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock != NULL) {
err = security_socket_shutdown(sock, how);
@@ -1789,6 +1828,9 @@ asmlinkage long sys_sendmsg(int fd, struct msghdr __user *msg, unsigned flags)
int err, ctl_len, iov_size, total_len;
int fput_needed;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
err = -EFAULT;
if (MSG_CMSG_COMPAT & flags) {
if (get_compat_msghdr(&msg_sys, msg_compat))
@@ -1896,6 +1938,9 @@ asmlinkage long sys_recvmsg(int fd, struct msghdr __user *msg,
struct sockaddr __user *uaddr;
int __user *uaddr_len;
+ if (update_current_cred() < 0)
+ return -ENOMEM;
+
if (MSG_CMSG_COMPAT & flags) {
if (get_compat_msghdr(&msg_sys, msg_compat))
return -EFAULT;
diff --git a/security/dummy.c b/security/dummy.c
index f535cc6..f403658 100644
--- a/security/dummy.c
+++ b/security/dummy.c
@@ -76,7 +76,13 @@ static int dummy_acct (struct file *file)
static int dummy_capable (struct task_struct *tsk, int cap)
{
- if (cap_raised (tsk->cap_effective, cap))
+ kernel_cap_t cap_effective;
+
+ rcu_read_lock();
+ cap_effective = task_cred(tsk)->cap_effective;
+ rcu_read_unlock();
+
+ if (cap_raised (cap_effective, cap))
return 0;
return -EPERM;
}
@@ -146,7 +152,12 @@ static void dummy_bprm_apply_creds (struct linux_binprm *bprm, int unsafe)
change_fsuid(bprm->cred, bprm->e_uid);
change_fsgid(bprm->cred, bprm->e_gid);
- dummy_capget(current, ¤t->cap_effective, ¤t->cap_inheritable, ¤t->cap_permitted);
+ dummy_capget(current,
+ ¤t->_cap_effective,
+ ¤t->cap_inheritable,
+ ¤t->cap_permitted);
+
+ change_cap(bprm->cred, current->_cap_effective);
}
static void dummy_bprm_post_apply_creds (struct linux_binprm *bprm)
@@ -499,7 +510,10 @@ static int dummy_task_setuid (uid_t id0, uid_t id1, uid_t id2, int flags)
static int dummy_task_post_setuid (uid_t id0, uid_t id1, uid_t id2, int flags)
{
- dummy_capget(current, ¤t->cap_effective, ¤t->cap_inheritable, ¤t->cap_permitted);
+ dummy_capget(current,
+ ¤t->_cap_effective,
+ ¤t->cap_inheritable,
+ ¤t->cap_permitted);
return 0;
}
@@ -696,7 +710,7 @@ static int dummy_sem_semop (struct sem_array *sma,
static int dummy_netlink_send (struct sock *sk, struct sk_buff *skb)
{
- NETLINK_CB(skb).eff_cap = current->cap_effective;
+ NETLINK_CB(skb).eff_cap = current->cred->cap_effective;
return 0;
}
The attached patch causes read_cache_pages() to release page-private data on a
page for which add_to_page_cache() fails or the filler function fails. This
permits pages with caching references associated with them to be cleaned up.
The invalidatepage() address space op is called (indirectly) to do the honours.
Signed-Off-By: David Howells <[email protected]>
---
mm/readahead.c | 40 ++++++++++++++++++++++++++++++++++++++--
1 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/mm/readahead.c b/mm/readahead.c
index 39bf45d..12d1378 100644
--- a/mm/readahead.c
+++ b/mm/readahead.c
@@ -15,6 +15,7 @@
#include <linux/backing-dev.h>
#include <linux/task_io_accounting_ops.h>
#include <linux/pagevec.h>
+#include <linux/buffer_head.h>
void default_unplug_io_fn(struct backing_dev_info *bdi, struct page *page)
{
@@ -51,6 +52,41 @@ EXPORT_SYMBOL_GPL(file_ra_state_init);
#define list_to_page(head) (list_entry((head)->prev, struct page, lru))
+/*
+ * see if a page needs releasing upon read_cache_pages() failure
+ * - the caller of read_cache_pages() may have set PG_private before calling,
+ * such as the NFS fs marking pages that are cached locally on disk, thus we
+ * need to give the fs a chance to clean up in the event of an error
+ */
+static void read_cache_pages_invalidate_page(struct address_space *mapping,
+ struct page *page)
+{
+ if (PagePrivate(page)) {
+ if (TestSetPageLocked(page))
+ BUG();
+ page->mapping = mapping;
+ do_invalidatepage(page, 0);
+ page->mapping = NULL;
+ unlock_page(page);
+ }
+ page_cache_release(page);
+}
+
+/*
+ * release a list of pages, invalidating them first if need be
+ */
+static void read_cache_pages_invalidate_pages(struct address_space *mapping,
+ struct list_head *pages)
+{
+ struct page *victim;
+
+ while (!list_empty(pages)) {
+ victim = list_to_page(pages);
+ list_del(&victim->lru);
+ read_cache_pages_invalidate_page(mapping, victim);
+ }
+}
+
/**
* read_cache_pages - populate an address space with some pages & start reads against them
* @mapping: the address_space
@@ -74,14 +110,14 @@ int read_cache_pages(struct address_space *mapping, struct list_head *pages,
page = list_to_page(pages);
list_del(&page->lru);
if (add_to_page_cache(page, mapping, page->index, GFP_KERNEL)) {
- page_cache_release(page);
+ read_cache_pages_invalidate_page(mapping, page);
continue;
}
ret = filler(data, page);
if (!pagevec_add(&lru_pvec, page))
__pagevec_lru_add(&lru_pvec);
if (ret) {
- put_pages_list(pages);
+ read_cache_pages_invalidate_pages(mapping, pages);
break;
}
task_io_account_read(PAGE_CACHE_SIZE);
Move into the cred struct the part of the task security data that defines how a
task acts upon an object. The part that defines how something acts upon a task
remains attached to the task.
For SELinux this requires some of task_security_struct to be split off into
cred_security_struct which is then attached to struct cred. Note that the
contents of cred_security_struct may not be changed without the generation of a
new struct cred.
The split is as follows:
(*) create_sid, keycreate_sid and sockcreate_sid just move across.
(*) sid is split into victim_sid - which remains - and action_sid - which
migrates.
(*) osid, exec_sid and ptrace_sid remain.
victim_sid is the SID used to govern actions upon the task. action_sid is used
to govern actions made by the task.
When accessing the cred_security_struct of another process, RCU read procedures
must be observed.
Signed-off-by: David Howells <[email protected]>
---
include/linux/cred.h | 1
include/linux/security.h | 33 ++
kernel/cred.c | 7 +
security/dummy.c | 11 +
security/selinux/exports.c | 6
security/selinux/hooks.c | 497 +++++++++++++++++++++++--------------
security/selinux/include/objsec.h | 16 +
security/selinux/selinuxfs.c | 8 -
security/selinux/xfrm.c | 6
9 files changed, 379 insertions(+), 206 deletions(-)
diff --git a/include/linux/cred.h b/include/linux/cred.h
index f3d98a8..98d5279 100644
--- a/include/linux/cred.h
+++ b/include/linux/cred.h
@@ -26,6 +26,7 @@ struct cred {
gid_t gid; /* fsgid as was */
struct rcu_head exterminate; /* cred destroyer */
struct group_info *group_info;
+ void *security;
/* caches for references to the three task keyrings
* - note that key_ref_t isn't typedef'd at this point, hence the odd
diff --git a/include/linux/security.h b/include/linux/security.h
index 1a15526..74cc204 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -504,6 +504,17 @@ struct request_sock;
* @file contains the file structure being received.
* Return 0 if permission is granted.
*
+ * Security hooks for credential structure operations.
+ *
+ * @cred_dup:
+ * Duplicate the credentials onto a duplicated cred structure.
+ * @cred points to the credentials structure. cred->security points to the
+ * security struct that was attached to the original cred struct, but it
+ * lacks a reference for the duplication if reference counting is needed.
+ * @cred_destroy:
+ * Destroy the credentials attached to a cred structure.
+ * @cred points to the credentials structure that is to be destroyed.
+ *
* Security hooks for task operations.
*
* @task_create:
@@ -1257,6 +1268,9 @@ struct security_operations {
struct fown_struct * fown, int sig);
int (*file_receive) (struct file * file);
+ int (*cred_dup)(struct cred *cred);
+ void (*cred_destroy)(struct cred *cred);
+
int (*task_create) (unsigned long clone_flags);
int (*task_alloc_security) (struct task_struct * p);
void (*task_free_security) (struct task_struct * p);
@@ -1864,6 +1878,16 @@ static inline int security_file_receive (struct file *file)
return security_ops->file_receive (file);
}
+static inline int security_cred_dup(struct cred *cred)
+{
+ return security_ops->cred_dup(cred);
+}
+
+static inline void security_cred_destroy(struct cred *cred)
+{
+ return security_ops->cred_destroy(cred);
+}
+
static inline int security_task_create (unsigned long clone_flags)
{
return security_ops->task_create (clone_flags);
@@ -2546,6 +2570,15 @@ static inline int security_file_receive (struct file *file)
return 0;
}
+static inline int security_cred_dup(struct cred *cred)
+{
+ return 0;
+}
+
+static inline void security_cred_destroy(struct cred *cred)
+{
+}
+
static inline int security_task_create (unsigned long clone_flags)
{
return 0;
diff --git a/kernel/cred.c b/kernel/cred.c
index 4710b60..5b827cb 100644
--- a/kernel/cred.c
+++ b/kernel/cred.c
@@ -92,6 +92,12 @@ struct cred *dup_cred(const struct cred *pcred)
if (likely(cred)) {
*cred = *pcred;
atomic_set(&cred->usage, 1);
+
+ if (security_cred_dup(cred) < 0) {
+ kfree(cred);
+ return NULL;
+ }
+
get_group_info(cred->group_info);
key_get(key_ref_to_ptr(cred->session_keyring));
key_get(key_ref_to_ptr(cred->process_keyring));
@@ -109,6 +115,7 @@ static void put_cred_rcu(struct rcu_head *rcu)
{
struct cred *cred = container_of(rcu, struct cred, exterminate);
+ security_cred_destroy(cred);
put_group_info(cred->group_info);
key_ref_put(cred->session_keyring);
key_ref_put(cred->process_keyring);
diff --git a/security/dummy.c b/security/dummy.c
index 62de89c..f535cc6 100644
--- a/security/dummy.c
+++ b/security/dummy.c
@@ -468,6 +468,15 @@ static int dummy_file_receive (struct file *file)
return 0;
}
+static int dummy_cred_dup(struct cred *cred)
+{
+ return 0;
+}
+
+static void dummy_cred_destroy(struct cred *cred)
+{
+}
+
static int dummy_task_create (unsigned long clone_flags)
{
return 0;
@@ -1038,6 +1047,8 @@ void security_fixup_ops (struct security_operations *ops)
set_to_dummy_if_null(ops, file_set_fowner);
set_to_dummy_if_null(ops, file_send_sigiotask);
set_to_dummy_if_null(ops, file_receive);
+ set_to_dummy_if_null(ops, cred_dup);
+ set_to_dummy_if_null(ops, cred_destroy);
set_to_dummy_if_null(ops, task_create);
set_to_dummy_if_null(ops, task_alloc_security);
set_to_dummy_if_null(ops, task_free_security);
diff --git a/security/selinux/exports.c b/security/selinux/exports.c
index b6f9694..29cb87a 100644
--- a/security/selinux/exports.c
+++ b/security/selinux/exports.c
@@ -57,7 +57,7 @@ void selinux_get_task_sid(struct task_struct *tsk, u32 *sid)
{
if (selinux_enabled) {
struct task_security_struct *tsec = tsk->security;
- *sid = tsec->sid;
+ *sid = tsec->victim_sid;
return;
}
*sid = 0;
@@ -77,9 +77,9 @@ EXPORT_SYMBOL_GPL(selinux_string_to_sid);
int selinux_relabel_packet_permission(u32 sid)
{
if (selinux_enabled) {
- struct task_security_struct *tsec = current->security;
+ struct cred_security_struct *csec = current->cred->security;
- return avc_has_perm(tsec->sid, sid, SECCLASS_PACKET,
+ return avc_has_perm(csec->action_sid, sid, SECCLASS_PACKET,
PACKET__RELABELTO, NULL);
}
return 0;
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 0753b20..4e72dbb 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -162,7 +162,8 @@ static int task_alloc_security(struct task_struct *task)
return -ENOMEM;
tsec->task = task;
- tsec->osid = tsec->sid = tsec->ptrace_sid = SECINITSID_UNLABELED;
+ tsec->osid = tsec->victim_sid = tsec->ptrace_sid =
+ SECINITSID_UNLABELED;
task->security = tsec;
return 0;
@@ -177,7 +178,7 @@ static void task_free_security(struct task_struct *task)
static int inode_alloc_security(struct inode *inode)
{
- struct task_security_struct *tsec = current->security;
+ struct cred_security_struct *csec = current->cred->security;
struct inode_security_struct *isec;
isec = kmem_cache_zalloc(sel_inode_cache, GFP_KERNEL);
@@ -189,7 +190,7 @@ static int inode_alloc_security(struct inode *inode)
isec->inode = inode;
isec->sid = SECINITSID_UNLABELED;
isec->sclass = SECCLASS_FILE;
- isec->task_sid = tsec->sid;
+ isec->task_sid = csec->action_sid;
inode->i_security = isec;
return 0;
@@ -211,7 +212,7 @@ static void inode_free_security(struct inode *inode)
static int file_alloc_security(struct file *file)
{
- struct task_security_struct *tsec = current->security;
+ struct cred_security_struct *csec = current->cred->security;
struct file_security_struct *fsec;
fsec = kzalloc(sizeof(struct file_security_struct), GFP_KERNEL);
@@ -219,8 +220,8 @@ static int file_alloc_security(struct file *file)
return -ENOMEM;
fsec->file = file;
- fsec->sid = tsec->sid;
- fsec->fown_sid = tsec->sid;
+ fsec->sid = csec->action_sid;
+ fsec->fown_sid = csec->action_sid;
file->f_security = fsec;
return 0;
@@ -335,26 +336,26 @@ static match_table_t tokens = {
static int may_context_mount_sb_relabel(u32 sid,
struct superblock_security_struct *sbsec,
- struct task_security_struct *tsec)
+ struct cred_security_struct *csec)
{
int rc;
- rc = avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM,
+ rc = avc_has_perm(csec->action_sid, sbsec->sid, SECCLASS_FILESYSTEM,
FILESYSTEM__RELABELFROM, NULL);
if (rc)
return rc;
- rc = avc_has_perm(tsec->sid, sid, SECCLASS_FILESYSTEM,
+ rc = avc_has_perm(csec->action_sid, sid, SECCLASS_FILESYSTEM,
FILESYSTEM__RELABELTO, NULL);
return rc;
}
static int may_context_mount_inode_relabel(u32 sid,
struct superblock_security_struct *sbsec,
- struct task_security_struct *tsec)
+ struct cred_security_struct *csec)
{
int rc;
- rc = avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM,
+ rc = avc_has_perm(csec->action_sid, sbsec->sid, SECCLASS_FILESYSTEM,
FILESYSTEM__RELABELFROM, NULL);
if (rc)
return rc;
@@ -371,7 +372,7 @@ static int try_context_mount(struct super_block *sb, void *data)
const char *name;
u32 sid;
int alloc = 0, rc = 0, seen = 0;
- struct task_security_struct *tsec = current->security;
+ struct cred_security_struct *csec = current->cred->security;
struct superblock_security_struct *sbsec = sb->s_security;
if (!data)
@@ -503,7 +504,7 @@ static int try_context_mount(struct super_block *sb, void *data)
goto out_free;
}
- rc = may_context_mount_sb_relabel(sid, sbsec, tsec);
+ rc = may_context_mount_sb_relabel(sid, sbsec, csec);
if (rc)
goto out_free;
@@ -525,12 +526,12 @@ static int try_context_mount(struct super_block *sb, void *data)
}
if (!fscontext) {
- rc = may_context_mount_sb_relabel(sid, sbsec, tsec);
+ rc = may_context_mount_sb_relabel(sid, sbsec, csec);
if (rc)
goto out_free;
sbsec->sid = sid;
} else {
- rc = may_context_mount_inode_relabel(sid, sbsec, tsec);
+ rc = may_context_mount_inode_relabel(sid, sbsec, csec);
if (rc)
goto out_free;
}
@@ -550,7 +551,7 @@ static int try_context_mount(struct super_block *sb, void *data)
goto out_free;
}
- rc = may_context_mount_inode_relabel(sid, sbsec, tsec);
+ rc = may_context_mount_inode_relabel(sid, sbsec, csec);
if (rc)
goto out_free;
@@ -570,7 +571,7 @@ static int try_context_mount(struct super_block *sb, void *data)
if (sid == sbsec->def_sid)
goto out_free;
- rc = may_context_mount_inode_relabel(sid, sbsec, tsec);
+ rc = may_context_mount_inode_relabel(sid, sbsec, csec);
if (rc)
goto out_free;
@@ -1025,15 +1026,22 @@ static inline u32 signal_to_av(int sig)
/* Check permission betweeen a pair of tasks, e.g. signal checks,
fork check, ptrace check, etc. */
-static int task_has_perm(struct task_struct *tsk1,
- struct task_struct *tsk2,
+static int task_has_perm(struct task_struct *actor,
+ struct task_struct *victim,
u32 perms)
{
- struct task_security_struct *tsec1, *tsec2;
+ struct cred_security_struct *csec;
+ struct task_security_struct *tsec;
+ u32 action_sid;
+
+ /* the actor may not be the current task */
+ rcu_read_lock();
+ csec = task_cred(actor)->security;
+ action_sid = csec->action_sid;
+ rcu_read_unlock();
- tsec1 = tsk1->security;
- tsec2 = tsk2->security;
- return avc_has_perm(tsec1->sid, tsec2->sid,
+ tsec = victim->security;
+ return avc_has_perm(action_sid, tsec->victim_sid,
SECCLASS_PROCESS, perms, NULL);
}
@@ -1041,16 +1049,16 @@ static int task_has_perm(struct task_struct *tsk1,
static int task_has_capability(struct task_struct *tsk,
int cap)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct avc_audit_data ad;
- tsec = tsk->security;
+ csec = tsk->cred->security;
AVC_AUDIT_DATA_INIT(&ad,CAP);
ad.tsk = tsk;
ad.u.cap = cap;
- return avc_has_perm(tsec->sid, tsec->sid,
+ return avc_has_perm(csec->action_sid, csec->action_sid,
SECCLASS_CAPABILITY, CAP_TO_MASK(cap), &ad);
}
@@ -1058,11 +1066,11 @@ static int task_has_capability(struct task_struct *tsk,
static int task_has_system(struct task_struct *tsk,
u32 perms)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
- tsec = tsk->security;
+ csec = tsk->cred->security;
- return avc_has_perm(tsec->sid, SECINITSID_KERNEL,
+ return avc_has_perm(csec->action_sid, SECINITSID_KERNEL,
SECCLASS_SYSTEM, perms, NULL);
}
@@ -1074,14 +1082,14 @@ static int inode_has_perm(struct task_struct *tsk,
u32 perms,
struct avc_audit_data *adp)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct inode_security_struct *isec;
struct avc_audit_data ad;
if (unlikely (IS_PRIVATE (inode)))
return 0;
- tsec = tsk->security;
+ csec = tsk->cred->security;
isec = inode->i_security;
if (!adp) {
@@ -1090,7 +1098,8 @@ static int inode_has_perm(struct task_struct *tsk,
ad.u.fs.inode = inode;
}
- return avc_has_perm(tsec->sid, isec->sid, isec->sclass, perms, adp);
+ return avc_has_perm(csec->action_sid, isec->sid, isec->sclass, perms,
+ adp);
}
/* Same as inode_has_perm, but pass explicit audit data containing
@@ -1121,7 +1130,7 @@ static int file_has_perm(struct task_struct *tsk,
struct file *file,
u32 av)
{
- struct task_security_struct *tsec = tsk->security;
+ struct cred_security_struct *csec = tsk->cred->security;
struct file_security_struct *fsec = file->f_security;
struct vfsmount *mnt = file->f_path.mnt;
struct dentry *dentry = file->f_path.dentry;
@@ -1133,8 +1142,8 @@ static int file_has_perm(struct task_struct *tsk,
ad.u.fs.mnt = mnt;
ad.u.fs.dentry = dentry;
- if (tsec->sid != fsec->sid) {
- rc = avc_has_perm(tsec->sid, fsec->sid,
+ if (csec->action_sid != fsec->sid) {
+ rc = avc_has_perm(csec->action_sid, fsec->sid,
SECCLASS_FD,
FD__USE,
&ad);
@@ -1154,36 +1163,36 @@ static int may_create(struct inode *dir,
struct dentry *dentry,
u16 tclass)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct inode_security_struct *dsec;
struct superblock_security_struct *sbsec;
u32 newsid;
struct avc_audit_data ad;
int rc;
- tsec = current->security;
+ csec = current->cred->security;
dsec = dir->i_security;
sbsec = dir->i_sb->s_security;
AVC_AUDIT_DATA_INIT(&ad, FS);
ad.u.fs.dentry = dentry;
- rc = avc_has_perm(tsec->sid, dsec->sid, SECCLASS_DIR,
+ rc = avc_has_perm(csec->action_sid, dsec->sid, SECCLASS_DIR,
DIR__ADD_NAME | DIR__SEARCH,
&ad);
if (rc)
return rc;
- if (tsec->create_sid && sbsec->behavior != SECURITY_FS_USE_MNTPOINT) {
- newsid = tsec->create_sid;
+ if (csec->create_sid && sbsec->behavior != SECURITY_FS_USE_MNTPOINT) {
+ newsid = csec->create_sid;
} else {
- rc = security_transition_sid(tsec->sid, dsec->sid, tclass,
- &newsid);
+ rc = security_transition_sid(csec->action_sid, dsec->sid,
+ tclass, &newsid);
if (rc)
return rc;
}
- rc = avc_has_perm(tsec->sid, newsid, tclass, FILE__CREATE, &ad);
+ rc = avc_has_perm(csec->action_sid, newsid, tclass, FILE__CREATE, &ad);
if (rc)
return rc;
@@ -1196,11 +1205,12 @@ static int may_create(struct inode *dir,
static int may_create_key(u32 ksid,
struct task_struct *ctx)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
- tsec = ctx->security;
+ csec = ctx->cred->security;
- return avc_has_perm(tsec->sid, ksid, SECCLASS_KEY, KEY__CREATE, NULL);
+ return avc_has_perm(csec->action_sid, ksid, SECCLASS_KEY, KEY__CREATE,
+ NULL);
}
#define MAY_LINK 0
@@ -1213,13 +1223,13 @@ static int may_link(struct inode *dir,
int kind)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct inode_security_struct *dsec, *isec;
struct avc_audit_data ad;
u32 av;
int rc;
- tsec = current->security;
+ csec = current->cred->security;
dsec = dir->i_security;
isec = dentry->d_inode->i_security;
@@ -1228,7 +1238,7 @@ static int may_link(struct inode *dir,
av = DIR__SEARCH;
av |= (kind ? DIR__REMOVE_NAME : DIR__ADD_NAME);
- rc = avc_has_perm(tsec->sid, dsec->sid, SECCLASS_DIR, av, &ad);
+ rc = avc_has_perm(csec->action_sid, dsec->sid, SECCLASS_DIR, av, &ad);
if (rc)
return rc;
@@ -1247,7 +1257,7 @@ static int may_link(struct inode *dir,
return 0;
}
- rc = avc_has_perm(tsec->sid, isec->sid, isec->sclass, av, &ad);
+ rc = avc_has_perm(csec->action_sid, isec->sid, isec->sclass, av, &ad);
return rc;
}
@@ -1256,14 +1266,14 @@ static inline int may_rename(struct inode *old_dir,
struct inode *new_dir,
struct dentry *new_dentry)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct inode_security_struct *old_dsec, *new_dsec, *old_isec, *new_isec;
struct avc_audit_data ad;
u32 av;
int old_is_dir, new_is_dir;
int rc;
- tsec = current->security;
+ csec = current->cred->security;
old_dsec = old_dir->i_security;
old_isec = old_dentry->d_inode->i_security;
old_is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
@@ -1272,16 +1282,16 @@ static inline int may_rename(struct inode *old_dir,
AVC_AUDIT_DATA_INIT(&ad, FS);
ad.u.fs.dentry = old_dentry;
- rc = avc_has_perm(tsec->sid, old_dsec->sid, SECCLASS_DIR,
+ rc = avc_has_perm(csec->action_sid, old_dsec->sid, SECCLASS_DIR,
DIR__REMOVE_NAME | DIR__SEARCH, &ad);
if (rc)
return rc;
- rc = avc_has_perm(tsec->sid, old_isec->sid,
+ rc = avc_has_perm(csec->action_sid, old_isec->sid,
old_isec->sclass, FILE__RENAME, &ad);
if (rc)
return rc;
if (old_is_dir && new_dir != old_dir) {
- rc = avc_has_perm(tsec->sid, old_isec->sid,
+ rc = avc_has_perm(csec->action_sid, old_isec->sid,
old_isec->sclass, DIR__REPARENT, &ad);
if (rc)
return rc;
@@ -1291,15 +1301,17 @@ static inline int may_rename(struct inode *old_dir,
av = DIR__ADD_NAME | DIR__SEARCH;
if (new_dentry->d_inode)
av |= DIR__REMOVE_NAME;
- rc = avc_has_perm(tsec->sid, new_dsec->sid, SECCLASS_DIR, av, &ad);
+ rc = avc_has_perm(csec->action_sid, new_dsec->sid, SECCLASS_DIR, av,
+ &ad);
if (rc)
return rc;
if (new_dentry->d_inode) {
new_isec = new_dentry->d_inode->i_security;
new_is_dir = S_ISDIR(new_dentry->d_inode->i_mode);
- rc = avc_has_perm(tsec->sid, new_isec->sid,
+ rc = avc_has_perm(csec->action_sid, new_isec->sid,
new_isec->sclass,
- (new_is_dir ? DIR__RMDIR : FILE__UNLINK), &ad);
+ (new_is_dir ? DIR__RMDIR : FILE__UNLINK),
+ &ad);
if (rc)
return rc;
}
@@ -1313,12 +1325,12 @@ static int superblock_has_perm(struct task_struct *tsk,
u32 perms,
struct avc_audit_data *ad)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct superblock_security_struct *sbsec;
- tsec = tsk->security;
+ csec = tsk->cred->security;
sbsec = sb->s_security;
- return avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM,
+ return avc_has_perm(csec->action_sid, sbsec->sid, SECCLASS_FILESYSTEM,
perms, ad);
}
@@ -1371,7 +1383,7 @@ static inline u32 file_to_av(struct file *file)
static int selinux_ptrace(struct task_struct *parent, struct task_struct *child)
{
- struct task_security_struct *psec = parent->security;
+ struct cred_security_struct *psec;
struct task_security_struct *csec = child->security;
int rc;
@@ -1381,8 +1393,12 @@ static int selinux_ptrace(struct task_struct *parent, struct task_struct *child)
rc = task_has_perm(parent, child, PROCESS__PTRACE);
/* Save the SID of the tracing process for later use in apply_creds. */
- if (!(child->ptrace & PT_PTRACED) && !rc)
- csec->ptrace_sid = psec->sid;
+ if (!(child->ptrace & PT_PTRACED) && !rc) {
+ rcu_read_lock();
+ psec = task_cred(parent)->security;
+ csec->ptrace_sid = psec->action_sid;
+ rcu_read_unlock();
+ }
return rc;
}
@@ -1472,7 +1488,7 @@ static int selinux_sysctl(ctl_table *table, int op)
{
int error = 0;
u32 av;
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
u32 tsid;
int rc;
@@ -1480,7 +1496,7 @@ static int selinux_sysctl(ctl_table *table, int op)
if (rc)
return rc;
- tsec = current->security;
+ csec = current->cred->security;
rc = selinux_sysctl_get_sid(table, (op == 0001) ?
SECCLASS_DIR : SECCLASS_FILE, &tsid);
@@ -1492,7 +1508,7 @@ static int selinux_sysctl(ctl_table *table, int op)
/* The op values are "defined" in sysctl.c, thereby creating
* a bad coupling between this module and sysctl.c */
if(op == 001) {
- error = avc_has_perm(tsec->sid, tsid,
+ error = avc_has_perm(csec->action_sid, tsid,
SECCLASS_DIR, DIR__SEARCH, NULL);
} else {
av = 0;
@@ -1501,7 +1517,7 @@ static int selinux_sysctl(ctl_table *table, int op)
if (op & 002)
av |= FILE__WRITE;
if (av)
- error = avc_has_perm(tsec->sid, tsid,
+ error = avc_has_perm(csec->action_sid, tsid,
SECCLASS_FILE, av, NULL);
}
@@ -1589,11 +1605,11 @@ static int selinux_syslog(int type)
static int selinux_vm_enough_memory(struct mm_struct *mm, long pages)
{
int rc, cap_sys_admin = 0;
- struct task_security_struct *tsec = current->security;
+ struct cred_security_struct *csec = current->cred->security;
rc = secondary_ops->capable(current, CAP_SYS_ADMIN);
if (rc == 0)
- rc = avc_has_perm_noaudit(tsec->sid, tsec->sid,
+ rc = avc_has_perm_noaudit(csec->action_sid, csec->action_sid,
SECCLASS_CAPABILITY,
CAP_TO_MASK(CAP_SYS_ADMIN),
0,
@@ -1626,6 +1642,7 @@ static int selinux_bprm_alloc_security(struct linux_binprm *bprm)
static int selinux_bprm_set_security(struct linux_binprm *bprm)
{
struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct inode *inode = bprm->file->f_path.dentry->d_inode;
struct inode_security_struct *isec;
struct bprm_security_struct *bsec;
@@ -1643,15 +1660,16 @@ static int selinux_bprm_set_security(struct linux_binprm *bprm)
return 0;
tsec = current->security;
+ csec = bprm->cred->security;
isec = inode->i_security;
/* Default to the current task SID. */
- bsec->sid = tsec->sid;
+ bsec->sid = csec->action_sid;
/* Reset fs, key, and sock SIDs on execve. */
- tsec->create_sid = 0;
- tsec->keycreate_sid = 0;
- tsec->sockcreate_sid = 0;
+ csec->create_sid = 0;
+ csec->keycreate_sid = 0;
+ csec->sockcreate_sid = 0;
if (tsec->exec_sid) {
newsid = tsec->exec_sid;
@@ -1659,7 +1677,7 @@ static int selinux_bprm_set_security(struct linux_binprm *bprm)
tsec->exec_sid = 0;
} else {
/* Check for a default transition on this program. */
- rc = security_transition_sid(tsec->sid, isec->sid,
+ rc = security_transition_sid(csec->action_sid, isec->sid,
SECCLASS_PROCESS, &newsid);
if (rc)
return rc;
@@ -1670,16 +1688,16 @@ static int selinux_bprm_set_security(struct linux_binprm *bprm)
ad.u.fs.dentry = bprm->file->f_path.dentry;
if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)
- newsid = tsec->sid;
+ newsid = csec->action_sid;
- if (tsec->sid == newsid) {
- rc = avc_has_perm(tsec->sid, isec->sid,
+ if (csec->action_sid == newsid) {
+ rc = avc_has_perm(csec->action_sid, isec->sid,
SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad);
if (rc)
return rc;
} else {
/* Check permissions for the transition. */
- rc = avc_has_perm(tsec->sid, newsid,
+ rc = avc_has_perm(csec->action_sid, newsid,
SECCLASS_PROCESS, PROCESS__TRANSITION, &ad);
if (rc)
return rc;
@@ -1711,11 +1729,11 @@ static int selinux_bprm_secureexec (struct linux_binprm *bprm)
struct task_security_struct *tsec = current->security;
int atsecure = 0;
- if (tsec->osid != tsec->sid) {
+ if (tsec->osid != tsec->victim_sid) {
/* Enable secure mode for SIDs transitions unless
the noatsecure permission is granted between
the two SIDs, i.e. ahp returns 0. */
- atsecure = avc_has_perm(tsec->osid, tsec->sid,
+ atsecure = avc_has_perm(tsec->osid, tsec->victim_sid,
SECCLASS_PROCESS,
PROCESS__NOATSECURE, NULL);
}
@@ -1825,6 +1843,7 @@ static inline void flush_unauthorized_files(struct files_struct * files)
static void selinux_bprm_apply_creds(struct linux_binprm *bprm, int unsafe)
{
struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct bprm_security_struct *bsec;
u32 sid;
int rc;
@@ -1832,17 +1851,17 @@ static void selinux_bprm_apply_creds(struct linux_binprm *bprm, int unsafe)
secondary_ops->bprm_apply_creds(bprm, unsafe);
tsec = current->security;
-
+ csec = bprm->cred->security;
bsec = bprm->security;
sid = bsec->sid;
- tsec->osid = tsec->sid;
+ tsec->osid = tsec->victim_sid;
bsec->unsafe = 0;
- if (tsec->sid != sid) {
+ if (tsec->victim_sid != sid) {
/* Check for shared state. If not ok, leave SID
unchanged and kill. */
if (unsafe & LSM_UNSAFE_SHARE) {
- rc = avc_has_perm(tsec->sid, sid, SECCLASS_PROCESS,
+ rc = avc_has_perm(tsec->victim_sid, sid, SECCLASS_PROCESS,
PROCESS__SHARE, NULL);
if (rc) {
bsec->unsafe = 1;
@@ -1861,7 +1880,9 @@ static void selinux_bprm_apply_creds(struct linux_binprm *bprm, int unsafe)
return;
}
}
- tsec->sid = sid;
+ if (csec->action_sid == tsec->victim_sid)
+ csec->action_sid = sid;
+ tsec->victim_sid = sid;
}
}
@@ -1883,7 +1904,7 @@ static void selinux_bprm_post_apply_creds(struct linux_binprm *bprm)
force_sig_specific(SIGKILL, current);
return;
}
- if (tsec->osid == tsec->sid)
+ if (tsec->osid == tsec->victim_sid)
return;
/* Close files for which the new task SID is not authorized. */
@@ -1895,7 +1916,7 @@ static void selinux_bprm_post_apply_creds(struct linux_binprm *bprm)
signals. This must occur _after_ the task SID has
been updated so that any kill done after the flush
will be checked against the new SID. */
- rc = avc_has_perm(tsec->osid, tsec->sid, SECCLASS_PROCESS,
+ rc = avc_has_perm(tsec->osid, tsec->victim_sid, SECCLASS_PROCESS,
PROCESS__SIGINH, NULL);
if (rc) {
memset(&itimer, 0, sizeof itimer);
@@ -1922,7 +1943,7 @@ static void selinux_bprm_post_apply_creds(struct linux_binprm *bprm)
than the default soft limit for cases where the default
is lower than the hard limit, e.g. RLIMIT_CORE or
RLIMIT_STACK.*/
- rc = avc_has_perm(tsec->osid, tsec->sid, SECCLASS_PROCESS,
+ rc = avc_has_perm(tsec->osid, tsec->victim_sid, SECCLASS_PROCESS,
PROCESS__RLIMITINH, NULL);
if (rc) {
for (i = 0; i < RLIM_NLIMITS; i++) {
@@ -2124,21 +2145,21 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
char **name, void **value,
size_t *len)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct inode_security_struct *dsec;
struct superblock_security_struct *sbsec;
u32 newsid, clen;
int rc;
char *namep = NULL, *context;
- tsec = current->security;
+ csec = current->cred->security;
dsec = dir->i_security;
sbsec = dir->i_sb->s_security;
- if (tsec->create_sid && sbsec->behavior != SECURITY_FS_USE_MNTPOINT) {
- newsid = tsec->create_sid;
+ if (csec->create_sid && sbsec->behavior != SECURITY_FS_USE_MNTPOINT) {
+ newsid = csec->create_sid;
} else {
- rc = security_transition_sid(tsec->sid, dsec->sid,
+ rc = security_transition_sid(csec->action_sid, dsec->sid,
inode_mode_to_security_class(inode->i_mode),
&newsid);
if (rc) {
@@ -2297,7 +2318,7 @@ static int selinux_inode_getattr(struct vfsmount *mnt, struct dentry *dentry)
static int selinux_inode_setxattr(struct dentry *dentry, char *name, void *value, size_t size, int flags)
{
- struct task_security_struct *tsec = current->security;
+ struct cred_security_struct *csec = current->cred->security;
struct inode *inode = dentry->d_inode;
struct inode_security_struct *isec = inode->i_security;
struct superblock_security_struct *sbsec;
@@ -2329,7 +2350,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, char *name, void *value
AVC_AUDIT_DATA_INIT(&ad,FS);
ad.u.fs.dentry = dentry;
- rc = avc_has_perm(tsec->sid, isec->sid, isec->sclass,
+ rc = avc_has_perm(csec->action_sid, isec->sid, isec->sclass,
FILE__RELABELFROM, &ad);
if (rc)
return rc;
@@ -2338,12 +2359,12 @@ static int selinux_inode_setxattr(struct dentry *dentry, char *name, void *value
if (rc)
return rc;
- rc = avc_has_perm(tsec->sid, newsid, isec->sclass,
+ rc = avc_has_perm(csec->action_sid, newsid, isec->sclass,
FILE__RELABELTO, &ad);
if (rc)
return rc;
- rc = security_validate_transition(isec->sid, newsid, tsec->sid,
+ rc = security_validate_transition(isec->sid, newsid, csec->action_sid,
isec->sclass);
if (rc)
return rc;
@@ -2577,8 +2598,9 @@ static int selinux_file_mmap(struct file *file, unsigned long reqprot,
unsigned long prot, unsigned long flags,
unsigned long addr, unsigned long addr_only)
{
+ struct cred_security_struct *csec = current->cred->security;
int rc = 0;
- u32 sid = ((struct task_security_struct*)(current->security))->sid;
+ u32 sid = csec->action_sid;
if (addr < mmap_min_addr)
rc = avc_has_perm(sid, sid, SECCLASS_MEMPROTECT,
@@ -2692,7 +2714,7 @@ static int selinux_file_set_fowner(struct file *file)
tsec = current->security;
fsec = file->f_security;
- fsec->fown_sid = tsec->sid;
+ fsec->fown_sid = tsec->victim_sid;
return 0;
}
@@ -2716,7 +2738,7 @@ static int selinux_file_send_sigiotask(struct task_struct *tsk,
else
perm = signal_to_av(signum);
- return avc_has_perm(fsec->fown_sid, tsec->sid,
+ return avc_has_perm(fsec->fown_sid, tsec->victim_sid,
SECCLASS_PROCESS, perm, NULL);
}
@@ -2725,6 +2747,31 @@ static int selinux_file_receive(struct file *file)
return file_has_perm(current, file, file_to_av(file));
}
+/* credential security operations */
+
+/*
+ * duplicate the security information attached to a credentials record that is
+ * itself undergoing duplication
+ */
+static int selinux_cred_dup(struct cred *cred)
+{
+ cred->security = kmemdup(cred->security,
+ sizeof(struct cred_security_struct),
+ GFP_KERNEL);
+ return cred->security ? 0 : -ENOMEM;
+}
+
+/*
+ * destroy the security information attached to a credentials record
+ * - this is done under RCU, and may not be associated with the task that set it
+ * up
+ */
+static void selinux_cred_destroy(struct cred *cred)
+{
+ kfree(cred->security);
+}
+
+
/* task security operations */
static int selinux_task_create(unsigned long clone_flags)
@@ -2751,13 +2798,10 @@ static int selinux_task_alloc_security(struct task_struct *tsk)
tsec2 = tsk->security;
tsec2->osid = tsec1->osid;
- tsec2->sid = tsec1->sid;
+ tsec2->victim_sid = tsec1->victim_sid;
- /* Retain the exec, fs, key, and sock SIDs across fork */
+ /* Retain the exec SID across fork */
tsec2->exec_sid = tsec1->exec_sid;
- tsec2->create_sid = tsec1->create_sid;
- tsec2->keycreate_sid = tsec1->keycreate_sid;
- tsec2->sockcreate_sid = tsec1->sockcreate_sid;
/* Retain ptracer SID across fork, if any.
This will be reset by the ptrace hook upon any
@@ -2895,7 +2939,8 @@ static int selinux_task_kill(struct task_struct *p, struct siginfo *info,
perm = signal_to_av(sig);
tsec = p->security;
if (secid)
- rc = avc_has_perm(secid, tsec->sid, SECCLASS_PROCESS, perm, NULL);
+ rc = avc_has_perm(secid, tsec->victim_sid,
+ SECCLASS_PROCESS, perm, NULL);
else
rc = task_has_perm(current, p, perm);
return rc;
@@ -2929,8 +2974,8 @@ static void selinux_task_reparent_to_init(struct task_struct *p)
secondary_ops->task_reparent_to_init(p);
tsec = p->security;
- tsec->osid = tsec->sid;
- tsec->sid = SECINITSID_KERNEL;
+ tsec->osid = tsec->victim_sid;
+ tsec->victim_sid = SECINITSID_KERNEL;
return;
}
@@ -2940,7 +2985,7 @@ static void selinux_task_to_inode(struct task_struct *p,
struct task_security_struct *tsec = p->security;
struct inode_security_struct *isec = inode->i_security;
- isec->sid = tsec->sid;
+ isec->sid = tsec->victim_sid;
isec->initialized = 1;
return;
}
@@ -3165,11 +3210,11 @@ static int socket_has_perm(struct task_struct *task, struct socket *sock,
u32 perms)
{
struct inode_security_struct *isec;
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct avc_audit_data ad;
int err = 0;
- tsec = task->security;
+ csec = task->cred->security;
isec = SOCK_INODE(sock)->i_security;
if (isec->sid == SECINITSID_KERNEL)
@@ -3177,7 +3222,8 @@ static int socket_has_perm(struct task_struct *task, struct socket *sock,
AVC_AUDIT_DATA_INIT(&ad,NET);
ad.u.net.sk = sock->sk;
- err = avc_has_perm(tsec->sid, isec->sid, isec->sclass, perms, &ad);
+ err = avc_has_perm(csec->action_sid, isec->sid, isec->sclass, perms,
+ &ad);
out:
return err;
@@ -3187,15 +3233,15 @@ static int selinux_socket_create(int family, int type,
int protocol, int kern)
{
int err = 0;
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
u32 newsid;
if (kern)
goto out;
- tsec = current->security;
- newsid = tsec->sockcreate_sid ? : tsec->sid;
- err = avc_has_perm(tsec->sid, newsid,
+ csec = current->cred->security;
+ newsid = csec->sockcreate_sid ? : csec->action_sid;
+ err = avc_has_perm(csec->action_sid, newsid,
socket_type_to_security_class(family, type,
protocol), SOCKET__CREATE, NULL);
@@ -3208,14 +3254,14 @@ static int selinux_socket_post_create(struct socket *sock, int family,
{
int err = 0;
struct inode_security_struct *isec;
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct sk_security_struct *sksec;
u32 newsid;
isec = SOCK_INODE(sock)->i_security;
- tsec = current->security;
- newsid = tsec->sockcreate_sid ? : tsec->sid;
+ csec = current->cred->security;
+ newsid = csec->sockcreate_sid ? : csec->action_sid;
isec->sclass = socket_type_to_security_class(family, type, protocol);
isec->sid = kern ? SECINITSID_KERNEL : newsid;
isec->initialized = 1;
@@ -4029,7 +4075,7 @@ static int ipc_alloc_security(struct task_struct *task,
struct kern_ipc_perm *perm,
u16 sclass)
{
- struct task_security_struct *tsec = task->security;
+ struct cred_security_struct *csec = task->cred->security;
struct ipc_security_struct *isec;
isec = kzalloc(sizeof(struct ipc_security_struct), GFP_KERNEL);
@@ -4038,7 +4084,7 @@ static int ipc_alloc_security(struct task_struct *task,
isec->sclass = sclass;
isec->ipc_perm = perm;
- isec->sid = tsec->sid;
+ isec->sid = csec->action_sid;
perm->security = isec;
return 0;
@@ -4077,17 +4123,18 @@ static void msg_msg_free_security(struct msg_msg *msg)
static int ipc_has_perm(struct kern_ipc_perm *ipc_perms,
u32 perms)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct ipc_security_struct *isec;
struct avc_audit_data ad;
- tsec = current->security;
+ csec = current->cred->security;
isec = ipc_perms->security;
AVC_AUDIT_DATA_INIT(&ad, IPC);
ad.u.ipc_id = ipc_perms->key;
- return avc_has_perm(tsec->sid, isec->sid, isec->sclass, perms, &ad);
+ return avc_has_perm(csec->action_sid, isec->sid, isec->sclass, perms,
+ &ad);
}
static int selinux_msg_msg_alloc_security(struct msg_msg *msg)
@@ -4103,7 +4150,7 @@ static void selinux_msg_msg_free_security(struct msg_msg *msg)
/* message queue security operations */
static int selinux_msg_queue_alloc_security(struct msg_queue *msq)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct ipc_security_struct *isec;
struct avc_audit_data ad;
int rc;
@@ -4112,13 +4159,13 @@ static int selinux_msg_queue_alloc_security(struct msg_queue *msq)
if (rc)
return rc;
- tsec = current->security;
+ csec = current->cred->security;
isec = msq->q_perm.security;
AVC_AUDIT_DATA_INIT(&ad, IPC);
ad.u.ipc_id = msq->q_perm.key;
- rc = avc_has_perm(tsec->sid, isec->sid, SECCLASS_MSGQ,
+ rc = avc_has_perm(csec->action_sid, isec->sid, SECCLASS_MSGQ,
MSGQ__CREATE, &ad);
if (rc) {
ipc_free_security(&msq->q_perm);
@@ -4134,17 +4181,17 @@ static void selinux_msg_queue_free_security(struct msg_queue *msq)
static int selinux_msg_queue_associate(struct msg_queue *msq, int msqflg)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct ipc_security_struct *isec;
struct avc_audit_data ad;
- tsec = current->security;
+ csec = current->cred->security;
isec = msq->q_perm.security;
AVC_AUDIT_DATA_INIT(&ad, IPC);
ad.u.ipc_id = msq->q_perm.key;
- return avc_has_perm(tsec->sid, isec->sid, SECCLASS_MSGQ,
+ return avc_has_perm(csec->action_sid, isec->sid, SECCLASS_MSGQ,
MSGQ__ASSOCIATE, &ad);
}
@@ -4178,13 +4225,13 @@ static int selinux_msg_queue_msgctl(struct msg_queue *msq, int cmd)
static int selinux_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, int msqflg)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct ipc_security_struct *isec;
struct msg_security_struct *msec;
struct avc_audit_data ad;
int rc;
- tsec = current->security;
+ csec = current->cred->security;
isec = msq->q_perm.security;
msec = msg->security;
@@ -4196,7 +4243,7 @@ static int selinux_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg,
* Compute new sid based on current process and
* message queue this message will be stored in
*/
- rc = security_transition_sid(tsec->sid,
+ rc = security_transition_sid(csec->action_sid,
isec->sid,
SECCLASS_MSG,
&msec->sid);
@@ -4208,11 +4255,11 @@ static int selinux_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg,
ad.u.ipc_id = msq->q_perm.key;
/* Can this process write to the queue? */
- rc = avc_has_perm(tsec->sid, isec->sid, SECCLASS_MSGQ,
+ rc = avc_has_perm(csec->action_sid, isec->sid, SECCLASS_MSGQ,
MSGQ__WRITE, &ad);
if (!rc)
/* Can this process send the message */
- rc = avc_has_perm(tsec->sid, msec->sid,
+ rc = avc_has_perm(csec->action_sid, msec->sid,
SECCLASS_MSG, MSG__SEND, &ad);
if (!rc)
/* Can the message be put in the queue? */
@@ -4239,10 +4286,10 @@ static int selinux_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg,
AVC_AUDIT_DATA_INIT(&ad, IPC);
ad.u.ipc_id = msq->q_perm.key;
- rc = avc_has_perm(tsec->sid, isec->sid,
+ rc = avc_has_perm(tsec->victim_sid, isec->sid,
SECCLASS_MSGQ, MSGQ__READ, &ad);
if (!rc)
- rc = avc_has_perm(tsec->sid, msec->sid,
+ rc = avc_has_perm(tsec->victim_sid, msec->sid,
SECCLASS_MSG, MSG__RECEIVE, &ad);
return rc;
}
@@ -4250,7 +4297,7 @@ static int selinux_msg_queue_msgrcv(struct msg_queue *msq, struct msg_msg *msg,
/* Shared Memory security operations */
static int selinux_shm_alloc_security(struct shmid_kernel *shp)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct ipc_security_struct *isec;
struct avc_audit_data ad;
int rc;
@@ -4259,13 +4306,13 @@ static int selinux_shm_alloc_security(struct shmid_kernel *shp)
if (rc)
return rc;
- tsec = current->security;
+ csec = current->cred->security;
isec = shp->shm_perm.security;
AVC_AUDIT_DATA_INIT(&ad, IPC);
ad.u.ipc_id = shp->shm_perm.key;
- rc = avc_has_perm(tsec->sid, isec->sid, SECCLASS_SHM,
+ rc = avc_has_perm(csec->action_sid, isec->sid, SECCLASS_SHM,
SHM__CREATE, &ad);
if (rc) {
ipc_free_security(&shp->shm_perm);
@@ -4281,17 +4328,17 @@ static void selinux_shm_free_security(struct shmid_kernel *shp)
static int selinux_shm_associate(struct shmid_kernel *shp, int shmflg)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct ipc_security_struct *isec;
struct avc_audit_data ad;
- tsec = current->security;
+ csec = current->cred->security;
isec = shp->shm_perm.security;
AVC_AUDIT_DATA_INIT(&ad, IPC);
ad.u.ipc_id = shp->shm_perm.key;
- return avc_has_perm(tsec->sid, isec->sid, SECCLASS_SHM,
+ return avc_has_perm(csec->action_sid, isec->sid, SECCLASS_SHM,
SHM__ASSOCIATE, &ad);
}
@@ -4349,7 +4396,7 @@ static int selinux_shm_shmat(struct shmid_kernel *shp,
/* Semaphore security operations */
static int selinux_sem_alloc_security(struct sem_array *sma)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct ipc_security_struct *isec;
struct avc_audit_data ad;
int rc;
@@ -4358,13 +4405,13 @@ static int selinux_sem_alloc_security(struct sem_array *sma)
if (rc)
return rc;
- tsec = current->security;
+ csec = current->cred->security;
isec = sma->sem_perm.security;
AVC_AUDIT_DATA_INIT(&ad, IPC);
ad.u.ipc_id = sma->sem_perm.key;
- rc = avc_has_perm(tsec->sid, isec->sid, SECCLASS_SEM,
+ rc = avc_has_perm(csec->action_sid, isec->sid, SECCLASS_SEM,
SEM__CREATE, &ad);
if (rc) {
ipc_free_security(&sma->sem_perm);
@@ -4380,17 +4427,17 @@ static void selinux_sem_free_security(struct sem_array *sma)
static int selinux_sem_associate(struct sem_array *sma, int semflg)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct ipc_security_struct *isec;
struct avc_audit_data ad;
- tsec = current->security;
+ csec = current->cred->security;
isec = sma->sem_perm.security;
AVC_AUDIT_DATA_INIT(&ad, IPC);
ad.u.ipc_id = sma->sem_perm.key;
- return avc_has_perm(tsec->sid, isec->sid, SECCLASS_SEM,
+ return avc_has_perm(csec->action_sid, isec->sid, SECCLASS_SEM,
SEM__ASSOCIATE, &ad);
}
@@ -4506,6 +4553,7 @@ static int selinux_getprocattr(struct task_struct *p,
char *name, char **value)
{
struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
u32 sid;
int error;
unsigned len;
@@ -4517,22 +4565,25 @@ static int selinux_getprocattr(struct task_struct *p,
}
tsec = p->security;
+ rcu_read_lock();
+ csec = task_cred(p)->security;
if (!strcmp(name, "current"))
- sid = tsec->sid;
+ sid = tsec->victim_sid;
else if (!strcmp(name, "prev"))
sid = tsec->osid;
else if (!strcmp(name, "exec"))
sid = tsec->exec_sid;
else if (!strcmp(name, "fscreate"))
- sid = tsec->create_sid;
+ sid = csec->create_sid;
else if (!strcmp(name, "keycreate"))
- sid = tsec->keycreate_sid;
+ sid = csec->keycreate_sid;
else if (!strcmp(name, "sockcreate"))
- sid = tsec->sockcreate_sid;
+ sid = csec->sockcreate_sid;
else
- return -EINVAL;
+ goto invalid;
+ rcu_read_unlock();
if (!sid)
return 0;
@@ -4540,13 +4591,20 @@ static int selinux_getprocattr(struct task_struct *p,
if (error)
return error;
return len;
+
+invalid:
+ rcu_read_unlock();
+ return -EINVAL;
}
static int selinux_setprocattr(struct task_struct *p,
char *name, void *value, size_t size)
{
struct task_security_struct *tsec;
- u32 sid = 0;
+ struct cred_security_struct *csec;
+ struct av_decision avd;
+ struct cred *cred;
+ u32 sid = 0, perm;
int error;
char *str = value;
@@ -4562,17 +4620,19 @@ static int selinux_setprocattr(struct task_struct *p,
* above restriction is ever removed.
*/
if (!strcmp(name, "exec"))
- error = task_has_perm(current, p, PROCESS__SETEXEC);
+ perm = PROCESS__SETEXEC;
else if (!strcmp(name, "fscreate"))
- error = task_has_perm(current, p, PROCESS__SETFSCREATE);
+ perm = PROCESS__SETFSCREATE;
else if (!strcmp(name, "keycreate"))
- error = task_has_perm(current, p, PROCESS__SETKEYCREATE);
+ perm = PROCESS__SETKEYCREATE;
else if (!strcmp(name, "sockcreate"))
- error = task_has_perm(current, p, PROCESS__SETSOCKCREATE);
+ perm = PROCESS__SETSOCKCREATE;
else if (!strcmp(name, "current"))
- error = task_has_perm(current, p, PROCESS__SETCURRENT);
+ perm = PROCESS__SETCURRENT;
else
- error = -EINVAL;
+ return -EINVAL;
+
+ error = task_has_perm(current, p, perm);
if (error)
return error;
@@ -4594,20 +4654,37 @@ static int selinux_setprocattr(struct task_struct *p,
checks and may_create for the file creation checks. The
operation will then fail if the context is not permitted. */
tsec = p->security;
- if (!strcmp(name, "exec"))
+ csec = p->cred->security;
+ switch (perm) {
+ case PROCESS__SETEXEC:
tsec->exec_sid = sid;
- else if (!strcmp(name, "fscreate"))
- tsec->create_sid = sid;
- else if (!strcmp(name, "keycreate")) {
+ break;
+
+ case PROCESS__SETKEYCREATE:
error = may_create_key(sid, p);
if (error)
return error;
- tsec->keycreate_sid = sid;
- } else if (!strcmp(name, "sockcreate"))
- tsec->sockcreate_sid = sid;
- else if (!strcmp(name, "current")) {
- struct av_decision avd;
+ case PROCESS__SETFSCREATE:
+ case PROCESS__SETSOCKCREATE:
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+ csec = cred->security;
+ switch (perm) {
+ case PROCESS__SETKEYCREATE:
+ csec->keycreate_sid = sid;
+ break;
+ case PROCESS__SETFSCREATE:
+ csec->create_sid = sid;
+ break;
+ case PROCESS__SETSOCKCREATE:
+ csec->sockcreate_sid = sid;
+ break;
+ }
+ set_current_cred(cred);
+ break;
+ case PROCESS__SETCURRENT:
if (sid == 0)
return -EINVAL;
@@ -4626,11 +4703,16 @@ static int selinux_setprocattr(struct task_struct *p,
}
/* Check permissions for the transition. */
- error = avc_has_perm(tsec->sid, sid, SECCLASS_PROCESS,
+ error = avc_has_perm(csec->action_sid, sid, SECCLASS_PROCESS,
PROCESS__DYNTRANSITION, NULL);
if (error)
return error;
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+ csec = cred->security;
+
/* Check for ptracing, and update the task SID if ok.
Otherwise, leave SID unchanged and fail. */
task_lock(p);
@@ -4638,20 +4720,25 @@ static int selinux_setprocattr(struct task_struct *p,
error = avc_has_perm_noaudit(tsec->ptrace_sid, sid,
SECCLASS_PROCESS,
PROCESS__PTRACE, 0, &avd);
- if (!error)
- tsec->sid = sid;
+ if (!error) {
+ csec->action_sid = tsec->victim_sid = sid;
+ }
task_unlock(p);
avc_audit(tsec->ptrace_sid, sid, SECCLASS_PROCESS,
PROCESS__PTRACE, &avd, error, NULL);
- if (error)
+ if (error) {
+ put_cred(cred);
return error;
+ }
} else {
- tsec->sid = sid;
+ csec->action_sid = tsec->victim_sid = sid;
task_unlock(p);
}
- }
- else
+ set_current_cred(cred);
+ break;
+ default:
return -EINVAL;
+ }
return size;
}
@@ -4671,18 +4758,21 @@ static void selinux_release_secctx(char *secdata, u32 seclen)
static int selinux_key_alloc(struct key *k, struct task_struct *tsk,
unsigned long flags)
{
- struct task_security_struct *tsec = tsk->security;
+ struct cred_security_struct *csec;
struct key_security_struct *ksec;
ksec = kzalloc(sizeof(struct key_security_struct), GFP_KERNEL);
if (!ksec)
return -ENOMEM;
+ rcu_read_lock();
+ csec = task_cred(tsk)->security;
ksec->obj = k;
- if (tsec->keycreate_sid)
- ksec->sid = tsec->keycreate_sid;
+ if (csec->keycreate_sid)
+ ksec->sid = csec->keycreate_sid;
else
- ksec->sid = tsec->sid;
+ ksec->sid = csec->action_sid;
+ rcu_read_unlock();
k->security = ksec;
return 0;
@@ -4697,17 +4787,13 @@ static void selinux_key_free(struct key *k)
}
static int selinux_key_permission(key_ref_t key_ref,
- struct task_struct *ctx,
- key_perm_t perm)
+ struct task_struct *ctx,
+ key_perm_t perm)
{
struct key *key;
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
struct key_security_struct *ksec;
-
- key = key_ref_to_ptr(key_ref);
-
- tsec = ctx->security;
- ksec = key->security;
+ u32 action_sid;
/* if no specific permissions are requested, we skip the
permission check. No serious, additional covert channels
@@ -4715,7 +4801,16 @@ static int selinux_key_permission(key_ref_t key_ref,
if (perm == 0)
return 0;
- return avc_has_perm(tsec->sid, ksec->sid,
+ key = key_ref_to_ptr(key_ref);
+
+ rcu_read_lock();
+ csec = task_cred(ctx)->security;
+ action_sid = csec->action_sid;
+ rcu_read_unlock();
+
+ ksec = key->security;
+
+ return avc_has_perm(action_sid, ksec->sid,
SECCLASS_KEY, perm, NULL);
}
@@ -4790,6 +4885,9 @@ static struct security_operations selinux_ops = {
.file_send_sigiotask = selinux_file_send_sigiotask,
.file_receive = selinux_file_receive,
+ .cred_dup = selinux_cred_dup,
+ .cred_destroy = selinux_cred_destroy,
+
.task_create = selinux_task_create,
.task_alloc_security = selinux_task_alloc_security,
.task_free_security = selinux_task_free_security,
@@ -4898,6 +4996,17 @@ static struct security_operations selinux_ops = {
#endif
};
+/*
+ * initial security credentials
+ * - attached to init_cred which is never released
+ */
+static struct cred_security_struct init_cred_sec = {
+ .action_sid = SECINITSID_KERNEL,
+ .create_sid = SECINITSID_UNLABELED,
+ .keycreate_sid = SECINITSID_UNLABELED,
+ .sockcreate_sid = SECINITSID_UNLABELED,
+};
+
static __init int selinux_init(void)
{
struct task_security_struct *tsec;
@@ -4909,11 +5018,15 @@ static __init int selinux_init(void)
printk(KERN_INFO "SELinux: Initializing.\n");
+ /* Set the security state for the initial credentials */
+ init_cred.security = &init_cred_sec;
+ BUG_ON(current->cred != &init_cred);
+
/* Set the security state for the initial task. */
if (task_alloc_security(current))
panic("SELinux: Failed to initialize initial task.\n");
tsec = current->security;
- tsec->osid = tsec->sid = SECINITSID_KERNEL;
+ tsec->osid = tsec->victim_sid = SECINITSID_KERNEL;
sel_inode_cache = kmem_cache_create("selinux_inode_security",
sizeof(struct inode_security_struct),
diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h
index 91b88f0..a1dbc1c 100644
--- a/security/selinux/include/objsec.h
+++ b/security/selinux/include/objsec.h
@@ -27,14 +27,22 @@
#include "flask.h"
#include "avc.h"
+/*
+ * the security parameters associated with the credentials record structure
+ * (struct cred::security)
+ */
+struct cred_security_struct {
+ u32 action_sid; /* perform action as SID */
+ u32 create_sid; /* filesystem object creation as SID */
+ u32 keycreate_sid; /* key creation as SID */
+ u32 sockcreate_sid; /* socket creation as SID */
+};
+
struct task_security_struct {
struct task_struct *task; /* back pointer to task object */
u32 osid; /* SID prior to last execve */
- u32 sid; /* current SID */
+ u32 victim_sid; /* current SID affecting victimisation of this task */
u32 exec_sid; /* exec SID */
- u32 create_sid; /* fscreate SID */
- u32 keycreate_sid; /* keycreate SID */
- u32 sockcreate_sid; /* fscreate SID */
u32 ptrace_sid; /* SID of ptrace parent */
};
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index c9e92da..9c6737f 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -77,13 +77,13 @@ extern void selnl_notify_setenforce(int val);
static int task_has_security(struct task_struct *tsk,
u32 perms)
{
- struct task_security_struct *tsec;
+ struct cred_security_struct *csec;
- tsec = tsk->security;
- if (!tsec)
+ csec = tsk->cred->security;
+ if (!csec)
return -EACCES;
- return avc_has_perm(tsec->sid, SECINITSID_SECURITY,
+ return avc_has_perm(csec->action_sid, SECINITSID_SECURITY,
SECCLASS_SECURITY, perms, NULL);
}
diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c
index ba715f4..902d302 100644
--- a/security/selinux/xfrm.c
+++ b/security/selinux/xfrm.c
@@ -240,7 +240,7 @@ static int selinux_xfrm_sec_ctx_alloc(struct xfrm_sec_ctx **ctxp,
/*
* Does the subject have permission to set security context?
*/
- rc = avc_has_perm(tsec->sid, ctx->ctx_sid,
+ rc = avc_has_perm(tsec->action_sid, ctx->ctx_sid,
SECCLASS_ASSOCIATION,
ASSOCIATION__SETCONTEXT, NULL);
if (rc)
@@ -341,7 +341,7 @@ int selinux_xfrm_policy_delete(struct xfrm_policy *xp)
int rc = 0;
if (ctx)
- rc = avc_has_perm(tsec->sid, ctx->ctx_sid,
+ rc = avc_has_perm(tsec->action_sid, ctx->ctx_sid,
SECCLASS_ASSOCIATION,
ASSOCIATION__SETCONTEXT, NULL);
@@ -383,7 +383,7 @@ int selinux_xfrm_state_delete(struct xfrm_state *x)
int rc = 0;
if (ctx)
- rc = avc_has_perm(tsec->sid, ctx->ctx_sid,
+ rc = avc_has_perm(tsec->action_sid, ctx->ctx_sid,
SECCLASS_ASSOCIATION,
ASSOCIATION__SETCONTEXT, NULL);
Introduce a copy on write credentials record (struct cred). The fsuid, fsgid,
supplementary groups list move into it (DAC security). The session, process
and thread keyrings are reflected in it, but don't primarily reside there as
they aren't per-thread and occasionally need to be instantiated or replaced by
other threads or processes.
The LSM security information (MAC security) does *not* migrate from task_struct
at this point, but will be addressed by a later patch.
task_struct then gains an RCU-governed pointer to the credentials as a
replacement to the members it lost.
struct file gains a pointer to (f_cred) and a reference on the cred struct that
the opener was using at the time the file was opened. This replaces f_uid and
f_gid.
To alter the credentials record, a copy must be made. This copy may then be
altered and then the pointer in the task_struct redirected to it. From that
point on the new record should be considered immutable.
In addition, the default setting of i_uid and i_gid to fsuid and fsgid has been
moved from the callers of new_inode() into new_inode() itself.
Signed-off-by: David Howells <[email protected]>
---
arch/x86_64/kernel/sys_x86_64.c | 4 +
fs/aio.c | 25 +++++
fs/anon_inodes.c | 2
fs/attr.c | 4 -
fs/compat.c | 65 +++++++++++++-
fs/compat_ioctl.c | 7 +
fs/dcookies.c | 11 +-
fs/devpts/inode.c | 6 +
fs/dquot.c | 2
fs/eventfd.c | 4 +
fs/eventpoll.c | 16 +++
fs/exec.c | 37 +++++++-
fs/ext3/balloc.c | 2
fs/ext3/ialloc.c | 4 -
fs/fcntl.c | 11 ++
fs/file_table.c | 3 -
fs/filesystems.c | 7 +
fs/inode.c | 6 +
fs/inotify_user.c | 12 +++
fs/ioctl.c | 7 +
fs/locks.c | 6 +
fs/namei.c | 48 ++++++++--
fs/namespace.c | 12 +++
fs/nfs/inode.c | 2
fs/nfsctl.c | 4 +
fs/open.c | 103 ++++++++++++++++++++--
fs/pipe.c | 2
fs/posix_acl.c | 4 -
fs/proc/array.c | 12 +--
fs/quota.c | 4 +
fs/ramfs/inode.c | 2
fs/read_write.c | 54 ++++++++++-
fs/readdir.c | 8 ++
fs/select.c | 4 +
fs/signalfd.c | 4 +
fs/splice.c | 12 +++
fs/stat.c | 19 ++++
fs/super.c | 4 +
fs/sync.c | 11 ++
fs/timerfd.c | 4 +
fs/utimes.c | 4 +
fs/xattr.c | 60 ++++++++++++-
include/linux/binfmts.h | 1
include/linux/cred.h | 172 +++++++++++++++++++++++++++++++++++++
include/linux/fs.h | 5 +
include/linux/init_task.h | 4 -
include/linux/sched.h | 7 +
include/linux/sunrpc/auth.h | 17 +---
kernel/Makefile | 2
kernel/auditsc.c | 13 ++-
kernel/cred.c | 179 ++++++++++++++++++++++++++++++++++++++
kernel/exit.c | 1
kernel/fork.c | 63 ++++++++++++-
kernel/kernel-int.h | 15 +++
kernel/sys.c | 144 ++++++++++++++++++++++---------
kernel/uid16.c | 7 +
mm/fadvise.c | 4 +
mm/filemap.c | 4 +
mm/fremap.c | 7 +
mm/madvise.c | 7 +
mm/msync.c | 7 +
mm/shmem.c | 6 -
net/socket.c | 2
net/sunrpc/auth.c | 25 +----
net/sunrpc/auth_gss/auth_gss.c | 6 +
net/sunrpc/auth_null.c | 4 -
net/sunrpc/auth_unix.c | 6 +
security/dummy.c | 13 ++-
security/keys/compat.c | 6 +
security/keys/key.c | 3 -
security/keys/keyctl.c | 16 +++
security/keys/permission.c | 16 ++-
security/keys/process_keys.c | 58 ++++--------
security/keys/request_key.c | 14 +--
security/keys/request_key_auth.c | 2
75 files changed, 1205 insertions(+), 249 deletions(-)
diff --git a/arch/x86_64/kernel/sys_x86_64.c b/arch/x86_64/kernel/sys_x86_64.c
index 4770b7a..6cb691e 100644
--- a/arch/x86_64/kernel/sys_x86_64.c
+++ b/arch/x86_64/kernel/sys_x86_64.c
@@ -43,6 +43,10 @@ asmlinkage long sys_mmap(unsigned long addr, unsigned long len, unsigned long pr
long error;
struct file * file;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EINVAL;
if (off & ~PAGE_MASK)
goto out;
diff --git a/fs/aio.c b/fs/aio.c
index dbe699e..4bfabc1 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -1249,6 +1249,10 @@ asmlinkage long sys_io_setup(unsigned nr_events, aio_context_t __user *ctxp)
unsigned long ctx;
long ret;
+ ret = update_current_cred();
+ if (ret < 0)
+ goto out;
+
ret = get_user(ctx, ctxp);
if (unlikely(ret))
goto out;
@@ -1284,6 +1288,12 @@ out:
asmlinkage long sys_io_destroy(aio_context_t ctx)
{
struct kioctx *ioctx = lookup_ioctx(ctx);
+ int ret;
+
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;;
+
if (likely(NULL != ioctx)) {
io_destroy(ioctx);
return 0;
@@ -1634,6 +1644,10 @@ asmlinkage long sys_io_submit(aio_context_t ctx_id, long nr,
long ret = 0;
int i;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;;
+
if (unlikely(nr < 0))
return -EINVAL;
@@ -1711,6 +1725,10 @@ asmlinkage long sys_io_cancel(aio_context_t ctx_id, struct iocb __user *iocb,
u32 key;
int ret;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;;
+
ret = get_user(key, &iocb->aio_key);
if (unlikely(ret))
return -EFAULT;
@@ -1771,8 +1789,13 @@ asmlinkage long sys_io_getevents(aio_context_t ctx_id,
struct timespec __user *timeout)
{
struct kioctx *ioctx = lookup_ioctx(ctx_id);
- long ret = -EINVAL;
+ long ret;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EINVAL;
if (likely(ioctx)) {
if (likely(min_nr <= nr && min_nr >= 0 && nr >= 0))
ret = read_events(ioctx, min_nr, nr, events, timeout);
diff --git a/fs/anon_inodes.c b/fs/anon_inodes.c
index b4a7588..921087b 100644
--- a/fs/anon_inodes.c
+++ b/fs/anon_inodes.c
@@ -163,8 +163,6 @@ static struct inode *anon_inode_mkinode(void)
*/
inode->i_state = I_DIRTY;
inode->i_mode = S_IRUSR | S_IWUSR;
- inode->i_uid = current->fsuid;
- inode->i_gid = current->fsgid;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
return inode;
}
diff --git a/fs/attr.c b/fs/attr.c
index f8dfc22..3e6b911 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -29,13 +29,13 @@ int inode_change_ok(struct inode *inode, struct iattr *attr)
/* Make sure a caller can chown. */
if ((ia_valid & ATTR_UID) &&
- (current->fsuid != inode->i_uid ||
+ (current->cred->uid != inode->i_uid ||
attr->ia_uid != inode->i_uid) && !capable(CAP_CHOWN))
goto error;
/* Make sure caller can chgrp. */
if ((ia_valid & ATTR_GID) &&
- (current->fsuid != inode->i_uid ||
+ (current->cred->uid != inode->i_uid ||
(!in_group_p(attr->ia_gid) && attr->ia_gid != inode->i_gid)) &&
!capable(CAP_CHOWN))
goto error;
diff --git a/fs/compat.c b/fs/compat.c
index 15078ce..0d21573 100644
--- a/fs/compat.c
+++ b/fs/compat.c
@@ -238,6 +238,10 @@ asmlinkage long compat_sys_statfs(const char __user *path, struct compat_statfs
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk(path, &nd);
if (!error) {
struct kstatfs tmp;
@@ -255,6 +259,10 @@ asmlinkage long compat_sys_fstatfs(unsigned int fd, struct compat_statfs __user
struct kstatfs tmp;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EBADF;
file = fget(fd);
if (!file)
@@ -303,6 +311,10 @@ asmlinkage long compat_sys_statfs64(const char __user *path, compat_size_t sz, s
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
if (sz != sizeof(*buf))
return -EINVAL;
@@ -326,6 +338,10 @@ asmlinkage long compat_sys_fstatfs64(unsigned int fd, compat_size_t sz, struct c
if (sz != sizeof(*buf))
return -EINVAL;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EBADF;
file = fget(fd);
if (!file)
@@ -397,6 +413,10 @@ asmlinkage long compat_sys_fcntl64(unsigned int fd, unsigned int cmd,
struct flock f;
long ret;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
switch (cmd) {
case F_GETLK:
case F_SETLK:
@@ -723,6 +743,10 @@ asmlinkage long compat_sys_mount(char __user * dev_name, char __user * dir_name,
char *dir_page;
int retval;
+ retval = update_current_cred();
+ if (retval < 0)
+ return retval;
+
retval = copy_mount_options (type, &type_page);
if (retval < 0)
goto out;
@@ -821,6 +845,10 @@ asmlinkage long compat_sys_old_readdir(unsigned int fd,
struct file *file;
struct compat_readdir_callback buf;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = -EBADF;
file = fget(fd);
if (!file)
@@ -900,6 +928,10 @@ asmlinkage long compat_sys_getdents(unsigned int fd,
struct compat_getdents_callback buf;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = -EFAULT;
if (!access_ok(VERIFY_WRITE, dirent, count))
goto out;
@@ -991,6 +1023,10 @@ asmlinkage long compat_sys_getdents64(unsigned int fd,
struct compat_getdents_callback64 buf;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = -EFAULT;
if (!access_ok(VERIFY_WRITE, dirent, count))
goto out;
@@ -1140,8 +1176,13 @@ asmlinkage ssize_t
compat_sys_readv(unsigned long fd, const struct compat_iovec __user *vec, unsigned long vlen)
{
struct file *file;
- ssize_t ret = -EBADF;
+ ssize_t ret;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EBADF;
file = fget(fd);
if (!file)
return -EBADF;
@@ -1164,8 +1205,13 @@ asmlinkage ssize_t
compat_sys_writev(unsigned long fd, const struct compat_iovec __user *vec, unsigned long vlen)
{
struct file *file;
- ssize_t ret = -EBADF;
+ ssize_t ret;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EBADF;
file = fget(fd);
if (!file)
return -EBADF;
@@ -1357,6 +1403,10 @@ int compat_do_execve(char * filename,
struct file *file;
int retval;
+ retval = update_current_cred();
+ if (retval < 0)
+ return retval;
+
retval = -ENOMEM;
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
if (!bprm)
@@ -1523,10 +1573,15 @@ int compat_core_sys_select(int n, compat_ulong_t __user *inp,
{
fd_set_bits fds;
void *bits;
- int size, max_fds, ret = -EINVAL;
+ int size, max_fds, ret;
struct fdtable *fdt;
long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EINVAL;
if (n < 0)
goto out_nofds;
@@ -2019,6 +2074,10 @@ asmlinkage long compat_sys_nfsservctl(int cmd,
mm_segment_t oldfs;
int err;
+ err = update_current_cred();
+ if (err < 0)
+ return err;
+
karg = kmalloc(sizeof(*karg), GFP_USER);
kres = kmalloc(sizeof(*kres), GFP_USER);
if(!karg || !kres) {
diff --git a/fs/compat_ioctl.c b/fs/compat_ioctl.c
index 5a5b711..3708b79 100644
--- a/fs/compat_ioctl.c
+++ b/fs/compat_ioctl.c
@@ -3567,10 +3567,15 @@ asmlinkage long compat_sys_ioctl(unsigned int fd, unsigned int cmd,
unsigned long arg)
{
struct file *filp;
- int error = -EBADF;
+ int error;
struct ioctl_trans *t;
int fput_needed;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
+ error = -EBADF;
filp = fget_light(fd, &fput_needed);
if (!filp)
goto out;
diff --git a/fs/dcookies.c b/fs/dcookies.c
index 792cbf5..5f1d5ac 100644
--- a/fs/dcookies.c
+++ b/fs/dcookies.c
@@ -146,12 +146,16 @@ out:
asmlinkage long sys_lookup_dcookie(u64 cookie64, char __user * buf, size_t len)
{
unsigned long cookie = (unsigned long)cookie64;
- int err = -EINVAL;
+ int err;
char * kbuf;
char * path;
size_t pathlen;
struct dcookie_struct * dcs;
+ err = update_current_cred();
+ if (err < 0)
+ return err;
+
/* we could leak path information to users
* without dir read permission without this
*/
@@ -160,10 +164,9 @@ asmlinkage long sys_lookup_dcookie(u64 cookie64, char __user * buf, size_t len)
mutex_lock(&dcookie_mutex);
- if (!is_live()) {
- err = -EINVAL;
+ err = -EINVAL;
+ if (!is_live())
goto out;
- }
if (!(dcs = find_dcookie(cookie)))
goto out;
diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c
index 06ef9a2..af6bcff 100644
--- a/fs/devpts/inode.c
+++ b/fs/devpts/inode.c
@@ -172,8 +172,10 @@ int devpts_pty_new(struct tty_struct *tty)
return -ENOMEM;
inode->i_ino = number+2;
- inode->i_uid = config.setuid ? config.uid : current->fsuid;
- inode->i_gid = config.setgid ? config.gid : current->fsgid;
+ if (config.setuid)
+ inode->i_uid = config.uid;
+ if (config.setgid)
+ inode->i_gid = config.gid;
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
init_special_inode(inode, S_IFCHR|config.mode, device);
inode->i_private = tty;
diff --git a/fs/dquot.c b/fs/dquot.c
index de9a29f..f1748c6 100644
--- a/fs/dquot.c
+++ b/fs/dquot.c
@@ -832,7 +832,7 @@ static inline int need_print_warning(struct dquot *dquot)
switch (dquot->dq_type) {
case USRQUOTA:
- return current->fsuid == dquot->dq_id;
+ return current->cred->uid == dquot->dq_id;
case GRPQUOTA:
return in_group_p(dquot->dq_id);
}
diff --git a/fs/eventfd.c b/fs/eventfd.c
index 2ce19c0..54621da 100644
--- a/fs/eventfd.c
+++ b/fs/eventfd.c
@@ -204,6 +204,10 @@ asmlinkage long sys_eventfd(unsigned int count)
struct file *file;
struct inode *inode;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
diff --git a/fs/eventpoll.c b/fs/eventpoll.c
index 77b9953..1ec21ba 100644
--- a/fs/eventpoll.c
+++ b/fs/eventpoll.c
@@ -1081,6 +1081,10 @@ asmlinkage long sys_epoll_create(int size)
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d)\n",
current, size));
+ error = update_current_cred();
+ if (error < 0)
+ goto error_return;
+
/*
* Sanity check on the size parameter, and create the internal data
* structure ( "struct eventpoll" ).
@@ -1128,6 +1132,10 @@ asmlinkage long sys_epoll_ctl(int epfd, int op, int fd,
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)\n",
current, epfd, op, fd, event));
+ error = update_current_cred();
+ if (error < 0)
+ goto error_return;
+
error = -EFAULT;
if (ep_op_has_event(op) &&
copy_from_user(&epds, event, sizeof(struct epoll_event)))
@@ -1224,6 +1232,10 @@ asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events,
DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d)\n",
current, epfd, events, maxevents, timeout));
+ error = update_current_cred();
+ if (error < 0)
+ goto error_return;
+
/* The maximum number of event must be greater than zero */
if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
return -EINVAL;
@@ -1279,6 +1291,10 @@ asmlinkage long sys_epoll_pwait(int epfd, struct epoll_event __user *events,
int error;
sigset_t ksigmask, sigsaved;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
/*
* If the caller wants a certain signal mask to be set during the wait,
* we apply it here.
diff --git a/fs/exec.c b/fs/exec.c
index 073b0b8..9adc60b 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -130,6 +130,10 @@ asmlinkage long sys_uselib(const char __user * library)
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = __user_path_lookup_open(library, LOOKUP_FOLLOW, &nd, FMODE_READ|FMODE_EXEC);
if (error)
goto out;
@@ -1137,6 +1141,11 @@ int prepare_binprm(struct linux_binprm *bprm)
}
}
+ /* prepare the new credentials */
+ bprm->cred = dup_cred(current->cred);
+ if (!bprm->cred)
+ return -ENOMEM;
+
/* fill in binprm security blob */
retval = security_bprm_set(bprm);
if (retval)
@@ -1178,7 +1187,9 @@ void compute_creds(struct linux_binprm *bprm)
task_lock(current);
unsafe = unsafe_exec(current);
security_bprm_apply_creds(bprm, unsafe);
+ set_current_cred(bprm->cred);
task_unlock(current);
+ bprm->cred = NULL;
security_bprm_post_apply_creds(bprm);
}
EXPORT_SYMBOL(compute_creds);
@@ -1344,6 +1355,10 @@ int do_execve(char * filename,
unsigned long env_p;
int retval;
+ retval = update_current_cred();
+ if (retval < 0)
+ return retval;
+
retval = -ENOMEM;
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
if (!bprm)
@@ -1409,6 +1424,8 @@ out:
free_arg_pages(bprm);
if (bprm->security)
security_bprm_free(bprm);
+ if (bprm->cred)
+ put_cred(bprm->cred);
out_mm:
if (bprm->mm)
@@ -1716,8 +1733,8 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
struct linux_binfmt * binfmt;
struct inode * inode;
struct file * file;
+ struct cred *cred;
int retval = 0;
- int fsuid = current->fsuid;
int flag = 0;
int ispipe = 0;
@@ -1732,6 +1749,10 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
goto fail;
}
+ cred = dup_cred(current->cred);
+ if (!cred)
+ goto fail;
+
/*
* We cannot trust fsuid as being the "true" uid of the
* process nor do we know its entire history. We only know it
@@ -1739,13 +1760,13 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
*/
if (get_dumpable(mm) == 2) { /* Setuid core dump mode */
flag = O_EXCL; /* Stop rewrite attacks */
- current->fsuid = 0; /* Dump root private */
+ change_fsuid(cred, 0); /* Dump root private */
}
set_dumpable(mm, 0);
retval = coredump_wait(exit_code);
if (retval < 0)
- goto fail;
+ goto fail_cred;
/*
* Clear any false indication of pending signals that might
@@ -1763,19 +1784,20 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
lock_kernel();
ispipe = format_corename(corename, core_pattern, signr);
unlock_kernel();
+ cred = __set_current_cred(cred);
if (ispipe) {
/* SIGPIPE can happen, but it's just never processed */
if(call_usermodehelper_pipe(corename+1, NULL, NULL, &file)) {
printk(KERN_INFO "Core dump to %s pipe failed\n",
corename);
- goto fail_unlock;
+ goto fail_restore_cred;
}
} else
file = filp_open(corename,
O_CREAT | 2 | O_NOFOLLOW | O_LARGEFILE | flag,
0600);
if (IS_ERR(file))
- goto fail_unlock;
+ goto fail_restore_cred;
inode = file->f_path.dentry->d_inode;
if (inode->i_nlink > 1)
goto close_fail; /* multiple links - don't dump */
@@ -1799,9 +1821,12 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
current->signal->group_exit_code |= 0x80;
close_fail:
filp_close(file, NULL);
+fail_restore_cred:
+ set_current_cred(cred);
fail_unlock:
- current->fsuid = fsuid;
complete_all(&mm->core_done);
+fail_cred:
+ put_cred(cred);
fail:
return retval;
}
diff --git a/fs/ext3/balloc.c b/fs/ext3/balloc.c
index ca8aee6..6c4e82f 100644
--- a/fs/ext3/balloc.c
+++ b/fs/ext3/balloc.c
@@ -1360,7 +1360,7 @@ static int ext3_has_free_blocks(struct ext3_sb_info *sbi)
free_blocks = percpu_counter_read_positive(&sbi->s_freeblocks_counter);
root_blocks = le32_to_cpu(sbi->s_es->s_r_blocks_count);
if (free_blocks < root_blocks + 1 && !capable(CAP_SYS_RESOURCE) &&
- sbi->s_resuid != current->fsuid &&
+ sbi->s_resuid != current->cred->uid &&
(sbi->s_resgid == 0 || !in_group_p (sbi->s_resgid))) {
return 0;
}
diff --git a/fs/ext3/ialloc.c b/fs/ext3/ialloc.c
index e45dbd6..c10ec0c 100644
--- a/fs/ext3/ialloc.c
+++ b/fs/ext3/ialloc.c
@@ -546,15 +546,13 @@ got:
percpu_counter_inc(&sbi->s_dirs_counter);
sb->s_dirt = 1;
- inode->i_uid = current->fsuid;
if (test_opt (sb, GRPID))
inode->i_gid = dir->i_gid;
else if (dir->i_mode & S_ISGID) {
inode->i_gid = dir->i_gid;
if (S_ISDIR(mode))
mode |= S_ISGID;
- } else
- inode->i_gid = current->fsgid;
+ }
inode->i_mode = mode;
inode->i_ino = ino;
diff --git a/fs/fcntl.c b/fs/fcntl.c
index 78b2ff0..042a52e 100644
--- a/fs/fcntl.c
+++ b/fs/fcntl.c
@@ -385,8 +385,13 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
{
struct file *filp;
- long err = -EBADF;
+ long err;
+
+ err = update_current_cred();
+ if (err < 0)
+ return err;
+ err = -EBADF;
filp = fget(fd);
if (!filp)
goto out;
@@ -410,6 +415,10 @@ asmlinkage long sys_fcntl64(unsigned int fd, unsigned int cmd, unsigned long arg
struct file * filp;
long err;
+ err = update_current_cred();
+ if (err < 0)
+ return err;
+
err = -EBADF;
filp = fget(fd);
if (!filp)
diff --git a/fs/file_table.c b/fs/file_table.c
index d17fd69..f4c772c 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -115,8 +115,7 @@ struct file *get_empty_filp(void)
INIT_LIST_HEAD(&f->f_u.fu_list);
atomic_set(&f->f_count, 1);
rwlock_init(&f->f_owner.lock);
- f->f_uid = tsk->fsuid;
- f->f_gid = tsk->fsgid;
+ f->f_cred = get_current_cred();
eventpoll_init_file(f);
/* f->f_version: 0 */
return f;
diff --git a/fs/filesystems.c b/fs/filesystems.c
index f37f872..93fa1be 100644
--- a/fs/filesystems.c
+++ b/fs/filesystems.c
@@ -179,8 +179,13 @@ static int fs_maxindex(void)
*/
asmlinkage long sys_sysfs(int option, unsigned long arg1, unsigned long arg2)
{
- int retval = -EINVAL;
+ int retval;
+ retval = update_current_cred();
+ if (retval < 0)
+ return retval;
+
+ retval = -EINVAL;
switch (option) {
case 1:
retval = fs_index((const char __user *) arg1);
diff --git a/fs/inode.c b/fs/inode.c
index 29f5068..d69815e 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -531,6 +531,10 @@ repeat:
* mapping_set_gfp_mask() must be called with suitable flags on the
* newly created inode's mapping
*
+ * The inode's user and group IDs are set to the current task's fsuid and
+ * fsgid respectively as a default, but this should be overridden by the
+ * caller as appropriate.
+ *
*/
struct inode *new_inode(struct super_block *sb)
{
@@ -553,6 +557,8 @@ struct inode *new_inode(struct super_block *sb)
inode->i_ino = ++last_ino;
inode->i_state = 0;
spin_unlock(&inode_lock);
+ inode->i_uid = current->cred->uid;
+ inode->i_gid = current->cred->gid;
}
return inode;
}
diff --git a/fs/inotify_user.c b/fs/inotify_user.c
index 9bf2f6c..cc89629 100644
--- a/fs/inotify_user.c
+++ b/fs/inotify_user.c
@@ -547,6 +547,10 @@ asmlinkage long sys_inotify_init(void)
struct file *filp;
int fd, ret;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
fd = get_unused_fd();
if (fd < 0)
return fd;
@@ -619,6 +623,10 @@ asmlinkage long sys_inotify_add_watch(int fd, const char __user *path, u32 mask)
int ret, fput_needed;
unsigned flags = 0;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
filp = fget_light(fd, &fput_needed);
if (unlikely(!filp))
return -EBADF;
@@ -660,6 +668,10 @@ asmlinkage long sys_inotify_rm_watch(int fd, u32 wd)
struct inotify_device *dev;
int ret, fput_needed;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
filp = fget_light(fd, &fput_needed);
if (unlikely(!filp))
return -EBADF;
diff --git a/fs/ioctl.c b/fs/ioctl.c
index c2a773e..7088480 100644
--- a/fs/ioctl.c
+++ b/fs/ioctl.c
@@ -157,9 +157,14 @@ int vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd, unsigned lon
asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)
{
struct file * filp;
- int error = -EBADF;
+ int error;
int fput_needed;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
+ error = -EBADF;
filp = fget_light(fd, &fput_needed);
if (!filp)
goto out;
diff --git a/fs/locks.c b/fs/locks.c
index c795eaa..a6767e0 100644
--- a/fs/locks.c
+++ b/fs/locks.c
@@ -1341,7 +1341,7 @@ int generic_setlease(struct file *filp, long arg, struct file_lock **flp)
struct inode *inode = dentry->d_inode;
int error, rdlease_count = 0, wrlease_count = 0;
- if ((current->fsuid != inode->i_uid) && !capable(CAP_LEASE))
+ if ((current->cred->uid != inode->i_uid) && !capable(CAP_LEASE))
return -EACCES;
if (!S_ISREG(inode->i_mode))
return -EINVAL;
@@ -1559,6 +1559,10 @@ asmlinkage long sys_flock(unsigned int fd, unsigned int cmd)
int can_sleep, unlock;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EBADF;
filp = fget(fd);
if (!filp)
diff --git a/fs/namei.c b/fs/namei.c
index a83160a..59fcae5 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -185,7 +185,7 @@ int generic_permission(struct inode *inode, int mask,
{
umode_t mode = inode->i_mode;
- if (current->fsuid == inode->i_uid)
+ if (current->cred->uid == inode->i_uid)
mode >>= 6;
else {
if (IS_POSIXACL(inode) && (mode & S_IRWXG) && check_acl) {
@@ -437,7 +437,7 @@ static int exec_permission_lite(struct inode *inode,
if (inode->i_op && inode->i_op->permission)
return -EAGAIN;
- if (current->fsuid == inode->i_uid)
+ if (current->cred->uid == inode->i_uid)
mode >>= 6;
else if (in_group_p(inode->i_gid))
mode >>= 3;
@@ -1406,9 +1406,9 @@ static inline int check_sticky(struct inode *dir, struct inode *inode)
{
if (!(dir->i_mode & S_ISVTX))
return 0;
- if (inode->i_uid == current->fsuid)
+ if (inode->i_uid == current->cred->uid)
return 0;
- if (dir->i_uid == current->fsuid)
+ if (dir->i_uid == current->cred->uid)
return 0;
return !capable(CAP_FOWNER);
}
@@ -1914,11 +1914,15 @@ int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
asmlinkage long sys_mknodat(int dfd, const char __user *filename, int mode,
unsigned dev)
{
- int error = 0;
+ int error;
char * tmp;
struct dentry * dentry;
struct nameidata nd;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
if (S_ISDIR(mode))
return -EPERM;
tmp = getname(filename);
@@ -1990,11 +1994,15 @@ int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
asmlinkage long sys_mkdirat(int dfd, const char __user *pathname, int mode)
{
- int error = 0;
+ int error;
char * tmp;
struct dentry *dentry;
struct nameidata nd;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
tmp = getname(pathname);
error = PTR_ERR(tmp);
if (IS_ERR(tmp))
@@ -2088,11 +2096,15 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry)
static long do_rmdir(int dfd, const char __user *pathname)
{
- int error = 0;
+ int error;
char * name;
struct dentry *dentry;
struct nameidata nd;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
name = getname(pathname);
if(IS_ERR(name))
return PTR_ERR(name);
@@ -2171,12 +2183,16 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry)
*/
static long do_unlinkat(int dfd, const char __user *pathname)
{
- int error = 0;
+ int error;
char * name;
struct dentry *dentry;
struct nameidata nd;
struct inode *inode = NULL;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
name = getname(pathname);
if(IS_ERR(name))
return PTR_ERR(name);
@@ -2256,12 +2272,16 @@ int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname, i
asmlinkage long sys_symlinkat(const char __user *oldname,
int newdfd, const char __user *newname)
{
- int error = 0;
+ int error;
char * from;
char * to;
struct dentry *dentry;
struct nameidata nd;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
from = getname(oldname);
if(IS_ERR(from))
return PTR_ERR(from);
@@ -2354,6 +2374,10 @@ asmlinkage long sys_linkat(int olddfd, const char __user *oldname,
if ((flags & ~AT_SYMLINK_FOLLOW) != 0)
return -EINVAL;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
to = getname(newname);
if (IS_ERR(to))
return PTR_ERR(to);
@@ -2541,12 +2565,16 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
static int do_rename(int olddfd, const char *oldname,
int newdfd, const char *newname)
{
- int error = 0;
+ int error;
struct dentry * old_dir, * new_dir;
struct dentry * old_dentry, *new_dentry;
struct dentry * trap;
struct nameidata oldnd, newnd;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = do_path_lookup(olddfd, oldname, LOOKUP_PARENT, &oldnd);
if (error)
goto exit;
diff --git a/fs/namespace.c b/fs/namespace.c
index ddbda13..62e2a07 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -633,6 +633,10 @@ asmlinkage long sys_umount(char __user * name, int flags)
struct nameidata nd;
int retval;
+ retval = update_current_cred();
+ if (retval < 0)
+ goto out;
+
retval = __user_walk(name, LOOKUP_FOLLOW, &nd);
if (retval)
goto out;
@@ -1537,6 +1541,10 @@ asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
unsigned long dev_page;
char *dir_page;
+ retval = update_current_cred();
+ if (retval < 0)
+ return retval;
+
retval = copy_mount_options(type, &type_page);
if (retval < 0)
return retval;
@@ -1670,6 +1678,10 @@ asmlinkage long sys_pivot_root(const char __user * new_root,
struct nameidata new_nd, old_nd, parent_nd, root_parent, user_nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 71a49c3..c99f654 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -285,8 +285,6 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
nfsi->change_attr = fattr->change_attr;
inode->i_size = nfs_size_to_loff_t(fattr->size);
inode->i_nlink = fattr->nlink;
- inode->i_uid = fattr->uid;
- inode->i_gid = fattr->gid;
if (fattr->valid & (NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4)) {
/*
* report the blocks in 512byte units
diff --git a/fs/nfsctl.c b/fs/nfsctl.c
index 51f1b31..c70c880 100644
--- a/fs/nfsctl.c
+++ b/fs/nfsctl.c
@@ -90,6 +90,10 @@ asmlinkage sys_nfsservctl(int cmd, struct nfsctl_arg __user *arg, void __user *r
int version;
int err;
+ err = update_current_cred();
+ if (err < 0)
+ return err;
+
if (copy_from_user(&version, &arg->ca_version, sizeof(int)))
return -EFAULT;
diff --git a/fs/open.c b/fs/open.c
index 1d9e5e9..0c05863 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -124,6 +124,10 @@ asmlinkage long sys_statfs(const char __user * path, struct statfs __user * buf)
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk(path, &nd);
if (!error) {
struct statfs tmp;
@@ -143,6 +147,11 @@ asmlinkage long sys_statfs64(const char __user *path, size_t sz, struct statfs64
if (sz != sizeof(*buf))
return -EINVAL;
+
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk(path, &nd);
if (!error) {
struct statfs64 tmp;
@@ -161,6 +170,10 @@ asmlinkage long sys_fstatfs(unsigned int fd, struct statfs __user * buf)
struct statfs tmp;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EBADF;
file = fget(fd);
if (!file)
@@ -182,6 +195,10 @@ asmlinkage long sys_fstatfs64(unsigned int fd, size_t sz, struct statfs64 __user
if (sz != sizeof(*buf))
return -EINVAL;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EBADF;
file = fget(fd);
if (!file)
@@ -226,6 +243,10 @@ static long do_sys_truncate(const char __user * path, loff_t length)
struct inode * inode;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = -EINVAL;
if (length < 0) /* sorry, but loff_t says... */
goto out;
@@ -295,6 +316,10 @@ static long do_sys_ftruncate(unsigned int fd, loff_t length, int small)
struct file * file;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EINVAL;
if (length < 0)
goto out;
@@ -364,6 +389,10 @@ asmlinkage long sys_fallocate(int fd, int mode, loff_t offset, loff_t len)
if (offset < 0 || len <= 0)
goto out;
+ ret = update_current_cred();
+ if (ret < 0)
+ goto out;
+
/* Return error if mode is not supported */
ret = -EOPNOTSUPP;
if (mode && !(mode & FALLOC_FL_KEEP_SIZE))
@@ -421,19 +450,30 @@ out:
asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode)
{
struct nameidata nd;
- int old_fsuid, old_fsgid;
kernel_cap_t old_cap;
+ struct cred *cred;
int res;
if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */
return -EINVAL;
- old_fsuid = current->fsuid;
- old_fsgid = current->fsgid;
+ res = update_current_cred();
+ if (res < 0)
+ return res;
+
old_cap = current->cap_effective;
- current->fsuid = current->uid;
- current->fsgid = current->gid;
+ if (current->cred->uid != current->uid ||
+ current->cred->gid != current->gid) {
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+
+ change_fsuid(cred, current->uid);
+ change_fsgid(cred, current->gid);
+ } else {
+ cred = get_current_cred();
+ }
/*
* Clear the capabilities if we switch to a non-root user
@@ -448,6 +488,7 @@ asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode)
else
current->cap_effective = current->cap_permitted;
+ cred = __set_current_cred(cred);
res = __user_walk_fd(dfd, filename, LOOKUP_FOLLOW|LOOKUP_ACCESS, &nd);
if (res)
goto out;
@@ -464,8 +505,7 @@ asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode)
out_path_release:
path_release(&nd);
out:
- current->fsuid = old_fsuid;
- current->fsgid = old_fsgid;
+ set_current_cred(cred);
current->cap_effective = old_cap;
return res;
@@ -481,6 +521,10 @@ asmlinkage long sys_chdir(const char __user * filename)
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = __user_walk(filename,
LOOKUP_FOLLOW|LOOKUP_DIRECTORY|LOOKUP_CHDIR, &nd);
if (error)
@@ -506,6 +550,10 @@ asmlinkage long sys_fchdir(unsigned int fd)
struct vfsmount *mnt;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = -EBADF;
file = fget(fd);
if (!file)
@@ -533,6 +581,10 @@ asmlinkage long sys_chroot(const char __user * filename)
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = __user_walk(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY | LOOKUP_NOALT, &nd);
if (error)
goto out;
@@ -559,9 +611,14 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode)
struct inode * inode;
struct dentry * dentry;
struct file * file;
- int err = -EBADF;
+ int err;
struct iattr newattrs;
+ err = update_current_cred();
+ if (err < 0)
+ goto out;
+
+ err = -EBADF;
file = fget(fd);
if (!file)
goto out;
@@ -599,6 +656,10 @@ asmlinkage long sys_fchmodat(int dfd, const char __user *filename,
int error;
struct iattr newattrs;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = __user_walk_fd(dfd, filename, LOOKUP_FOLLOW, &nd);
if (error)
goto out;
@@ -671,6 +732,10 @@ asmlinkage long sys_chown(const char __user * filename, uid_t user, gid_t group)
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = user_path_walk(filename, &nd);
if (error)
goto out;
@@ -690,6 +755,10 @@ asmlinkage long sys_fchownat(int dfd, const char __user *filename, uid_t user,
if ((flag & ~AT_SYMLINK_NOFOLLOW) != 0)
goto out;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
follow = (flag & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
error = __user_walk_fd(dfd, filename, follow, &nd);
if (error)
@@ -705,6 +774,10 @@ asmlinkage long sys_lchown(const char __user * filename, uid_t user, gid_t group
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = user_path_walk_link(filename, &nd);
if (error)
goto out;
@@ -718,9 +791,14 @@ out:
asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group)
{
struct file * file;
- int error = -EBADF;
+ int error;
struct dentry * dentry;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
+ error = -EBADF;
file = fget(fd);
if (!file)
goto out;
@@ -1026,6 +1104,11 @@ long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
+ int ret;
+
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
if (!IS_ERR(tmp)) {
fd = get_unused_fd_flags(flags);
@@ -1121,6 +1204,8 @@ asmlinkage long sys_close(unsigned int fd)
struct fdtable *fdt;
int retval;
+ update_current_cred();
+
spin_lock(&files->file_lock);
fdt = files_fdtable(files);
if (fd >= fdt->max_fds)
diff --git a/fs/pipe.c b/fs/pipe.c
index 6b3d91a..18bf2ce 100644
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -939,8 +939,6 @@ static struct inode * get_pipe_inode(void)
*/
inode->i_state = I_DIRTY;
inode->i_mode = S_IFIFO | S_IRUSR | S_IWUSR;
- inode->i_uid = current->fsuid;
- inode->i_gid = current->fsgid;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
return inode;
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index aec931e..b36c79f 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -217,11 +217,11 @@ posix_acl_permission(struct inode *inode, const struct posix_acl *acl, int want)
switch(pa->e_tag) {
case ACL_USER_OBJ:
/* (May have been checked already) */
- if (inode->i_uid == current->fsuid)
+ if (inode->i_uid == current->cred->uid)
goto check_perm;
break;
case ACL_USER:
- if (pa->e_id == current->fsuid)
+ if (pa->e_id == current->cred->uid)
goto mask;
break;
case ACL_GROUP_OBJ:
diff --git a/fs/proc/array.c b/fs/proc/array.c
index ee4814d..dc2f83a 100644
--- a/fs/proc/array.c
+++ b/fs/proc/array.c
@@ -159,10 +159,12 @@ static inline const char *get_task_state(struct task_struct *tsk)
static inline char *task_state(struct task_struct *p, char *buffer)
{
struct group_info *group_info;
+ struct cred *cred;
int g;
struct fdtable *fdt = NULL;
rcu_read_lock();
+ cred = get_task_cred(p);
buffer += sprintf(buffer,
"State:\t%s\n"
"Tgid:\t%d\n"
@@ -175,8 +177,8 @@ static inline char *task_state(struct task_struct *p, char *buffer)
p->tgid, p->pid,
pid_alive(p) ? rcu_dereference(p->real_parent)->tgid : 0,
pid_alive(p) && p->ptrace ? rcu_dereference(p->parent)->pid : 0,
- p->uid, p->euid, p->suid, p->fsuid,
- p->gid, p->egid, p->sgid, p->fsgid);
+ p->uid, p->euid, p->suid, cred->uid,
+ p->gid, p->egid, p->sgid, cred->gid);
task_lock(p);
if (p->files)
@@ -186,14 +188,12 @@ static inline char *task_state(struct task_struct *p, char *buffer)
"Groups:\t",
fdt ? fdt->max_fds : 0);
rcu_read_unlock();
-
- group_info = p->group_info;
- get_group_info(group_info);
task_unlock(p);
+ group_info = cred->group_info;
for (g = 0; g < min(group_info->ngroups, NGROUPS_SMALL); g++)
buffer += sprintf(buffer, "%d ", GROUP_AT(group_info, g));
- put_group_info(group_info);
+ put_cred(cred);
buffer += sprintf(buffer, "\n");
return buffer;
diff --git a/fs/quota.c b/fs/quota.c
index 99b24b5..26983bc 100644
--- a/fs/quota.c
+++ b/fs/quota.c
@@ -369,6 +369,10 @@ asmlinkage long sys_quotactl(unsigned int cmd, const char __user *special, qid_t
struct super_block *sb = NULL;
int ret;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
cmds = cmd >> SUBCMDSHIFT;
type = cmd & SUBCMDMASK;
diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c
index ef2b46d..8c92fe0 100644
--- a/fs/ramfs/inode.c
+++ b/fs/ramfs/inode.c
@@ -55,8 +55,6 @@ struct inode *ramfs_get_inode(struct super_block *sb, int mode, dev_t dev)
if (inode) {
inode->i_mode = mode;
- inode->i_uid = current->fsuid;
- inode->i_gid = current->fsgid;
inode->i_blocks = 0;
inode->i_mapping->a_ops = &ramfs_aops;
inode->i_mapping->backing_dev_info = &ramfs_backing_dev_info;
diff --git a/fs/read_write.c b/fs/read_write.c
index 507ddff..2c6aa3d 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -134,6 +134,10 @@ asmlinkage off_t sys_lseek(unsigned int fd, off_t offset, unsigned int origin)
struct file * file;
int fput_needed;
+ retval = update_current_cred();
+ if (retval < 0)
+ return retval;
+
retval = -EBADF;
file = fget_light(fd, &fput_needed);
if (!file)
@@ -161,6 +165,10 @@ asmlinkage long sys_llseek(unsigned int fd, unsigned long offset_high,
loff_t offset;
int fput_needed;
+ retval = update_current_cred();
+ if (retval < 0)
+ return retval;
+
retval = -EBADF;
file = fget_light(fd, &fput_needed);
if (!file)
@@ -357,9 +365,14 @@ static inline void file_pos_write(struct file *file, loff_t pos)
asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
struct file *file;
- ssize_t ret = -EBADF;
+ ssize_t ret;
int fput_needed;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EBADF;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
@@ -375,9 +388,14 @@ EXPORT_SYMBOL_GPL(sys_read);
asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t count)
{
struct file *file;
- ssize_t ret = -EBADF;
+ ssize_t ret;
int fput_needed;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EBADF;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
@@ -393,12 +411,17 @@ asmlinkage ssize_t sys_pread64(unsigned int fd, char __user *buf,
size_t count, loff_t pos)
{
struct file *file;
- ssize_t ret = -EBADF;
+ ssize_t ret;
int fput_needed;
if (pos < 0)
return -EINVAL;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EBADF;
file = fget_light(fd, &fput_needed);
if (file) {
ret = -ESPIPE;
@@ -414,12 +437,17 @@ asmlinkage ssize_t sys_pwrite64(unsigned int fd, const char __user *buf,
size_t count, loff_t pos)
{
struct file *file;
- ssize_t ret = -EBADF;
+ ssize_t ret;
int fput_needed;
if (pos < 0)
return -EINVAL;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EBADF;
file = fget_light(fd, &fput_needed);
if (file) {
ret = -ESPIPE;
@@ -664,9 +692,14 @@ asmlinkage ssize_t
sys_readv(unsigned long fd, const struct iovec __user *vec, unsigned long vlen)
{
struct file *file;
- ssize_t ret = -EBADF;
+ ssize_t ret;
int fput_needed;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EBADF;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
@@ -685,9 +718,14 @@ asmlinkage ssize_t
sys_writev(unsigned long fd, const struct iovec __user *vec, unsigned long vlen)
{
struct file *file;
- ssize_t ret = -EBADF;
+ ssize_t ret;
int fput_needed;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
+ ret = -EBADF;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
@@ -711,6 +749,10 @@ static ssize_t do_sendfile(int out_fd, int in_fd, loff_t *ppos,
ssize_t retval;
int fput_needed_in, fput_needed_out, fl;
+ retval = update_current_cred();
+ if (retval < 0)
+ return retval;
+
/*
* Get input file, and verify that it is ok..
*/
diff --git a/fs/readdir.c b/fs/readdir.c
index efe52e6..57e6aa9 100644
--- a/fs/readdir.c
+++ b/fs/readdir.c
@@ -187,6 +187,10 @@ asmlinkage long sys_getdents(unsigned int fd, struct linux_dirent __user * diren
struct getdents_callback buf;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = -EFAULT;
if (!access_ok(VERIFY_WRITE, dirent, count))
goto out;
@@ -271,6 +275,10 @@ asmlinkage long sys_getdents64(unsigned int fd, struct linux_dirent64 __user * d
struct getdents_callback64 buf;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = -EFAULT;
if (!access_ok(VERIFY_WRITE, dirent, count))
goto out;
diff --git a/fs/select.c b/fs/select.c
index 46dca31..e2357c8 100644
--- a/fs/select.c
+++ b/fs/select.c
@@ -661,6 +661,10 @@ int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
struct poll_list *stack_pp = NULL;
+ err = update_current_cred();
+ if (err < 0)
+ return err;
+
/* Do a sanity check on nfds ... */
if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
return -EINVAL;
diff --git a/fs/signalfd.c b/fs/signalfd.c
index aefb0be..a8316e3 100644
--- a/fs/signalfd.c
+++ b/fs/signalfd.c
@@ -207,6 +207,10 @@ asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemas
struct file *file;
struct inode *inode;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
if (sizemask != sizeof(sigset_t) ||
copy_from_user(&sigmask, user_mask, sizeof(sigmask)))
return -EINVAL;
diff --git a/fs/splice.c b/fs/splice.c
index c010a72..ceb1f07 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -1510,6 +1510,10 @@ asmlinkage long sys_vmsplice(int fd, const struct iovec __user *iov,
else if (unlikely(!nr_segs))
return 0;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EBADF;
file = fget_light(fd, &fput);
if (file) {
@@ -1535,6 +1539,10 @@ asmlinkage long sys_splice(int fd_in, loff_t __user *off_in,
if (unlikely(!len))
return 0;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EBADF;
in = fget_light(fd_in, &fput_in);
if (in) {
@@ -1752,6 +1760,10 @@ asmlinkage long sys_tee(int fdin, int fdout, size_t len, unsigned int flags)
if (unlikely(!len))
return 0;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = -EBADF;
in = fget_light(fdin, &fput_in);
if (in) {
diff --git a/fs/stat.c b/fs/stat.c
index 6851006..ea8e08d 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -60,6 +60,10 @@ int vfs_stat_fd(int dfd, char __user *name, struct kstat *stat)
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = __user_walk_fd(dfd, name, LOOKUP_FOLLOW, &nd);
if (!error) {
error = vfs_getattr(nd.mnt, nd.dentry, stat);
@@ -80,6 +84,10 @@ int vfs_lstat_fd(int dfd, char __user *name, struct kstat *stat)
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = __user_walk_fd(dfd, name, 0, &nd);
if (!error) {
error = vfs_getattr(nd.mnt, nd.dentry, stat);
@@ -98,8 +106,13 @@ EXPORT_SYMBOL(vfs_lstat);
int vfs_fstat(unsigned int fd, struct kstat *stat)
{
struct file *f = fget(fd);
- int error = -EBADF;
+ int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
+ error = -EBADF;
if (f) {
error = vfs_getattr(f->f_path.mnt, f->f_path.dentry, stat);
fput(f);
@@ -300,6 +313,10 @@ asmlinkage long sys_readlinkat(int dfd, const char __user *path,
if (bufsiz <= 0)
return -EINVAL;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = __user_walk_fd(dfd, path, 0, &nd);
if (!error) {
struct inode * inode = nd.dentry->d_inode;
diff --git a/fs/super.c b/fs/super.c
index fc8ebed..28e7370 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -540,6 +540,10 @@ asmlinkage long sys_ustat(unsigned dev, struct ustat __user * ubuf)
struct kstatfs sbuf;
int err = -EINVAL;
+ err = update_current_cred();
+ if (err < 0)
+ return err;
+
s = user_get_super(new_decode_dev(dev));
if (s == NULL)
goto out;
diff --git a/fs/sync.c b/fs/sync.c
index 7cd005e..d5e2d5f 100644
--- a/fs/sync.c
+++ b/fs/sync.c
@@ -108,8 +108,13 @@ out:
static long __do_fsync(unsigned int fd, int datasync)
{
struct file *file;
- int ret = -EBADF;
+ int ret;
+
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+ ret = -EBADF;
file = fget(fd);
if (file) {
ret = do_fsync(file, datasync);
@@ -183,6 +188,10 @@ asmlinkage long sys_sync_file_range(int fd, loff_t offset, loff_t nbytes,
int fput_needed;
umode_t i_mode;
+ ret = update_current_cred();
+ if (ret < 0)
+ goto out;
+
ret = -EINVAL;
if (flags & ~VALID_FLAGS)
goto out;
diff --git a/fs/timerfd.c b/fs/timerfd.c
index 61983f3..3f95c7d 100644
--- a/fs/timerfd.c
+++ b/fs/timerfd.c
@@ -159,6 +159,10 @@ asmlinkage long sys_timerfd(int ufd, int clockid, int flags,
struct inode *inode;
struct itimerspec ktmr;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
if (copy_from_user(&ktmr, utmr, sizeof(ktmr)))
return -EFAULT;
diff --git a/fs/utimes.c b/fs/utimes.c
index 682eb63..409e433 100644
--- a/fs/utimes.c
+++ b/fs/utimes.c
@@ -51,6 +51,10 @@ long do_utimes(int dfd, char __user *filename, struct timespec *times, int flags
struct iattr newattrs;
struct file *f = NULL;
+ error = update_current_cred();
+ if (error < 0)
+ goto out;
+
error = -EINVAL;
if (flags & ~AT_SYMLINK_NOFOLLOW)
goto out;
diff --git a/fs/xattr.c b/fs/xattr.c
index a44fd92..3137912 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -232,6 +232,10 @@ sys_setxattr(char __user *path, char __user *name, void __user *value,
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk(path, &nd);
if (error)
return error;
@@ -247,6 +251,10 @@ sys_lsetxattr(char __user *path, char __user *name, void __user *value,
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk_link(path, &nd);
if (error)
return error;
@@ -261,8 +269,13 @@ sys_fsetxattr(int fd, char __user *name, void __user *value,
{
struct file *f;
struct dentry *dentry;
- int error = -EBADF;
+ int error;
+
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+ error = -EBADF;
f = fget(fd);
if (!f)
return error;
@@ -317,6 +330,10 @@ sys_getxattr(char __user *path, char __user *name, void __user *value,
struct nameidata nd;
ssize_t error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk(path, &nd);
if (error)
return error;
@@ -332,6 +349,10 @@ sys_lgetxattr(char __user *path, char __user *name, void __user *value,
struct nameidata nd;
ssize_t error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk_link(path, &nd);
if (error)
return error;
@@ -344,8 +365,13 @@ asmlinkage ssize_t
sys_fgetxattr(int fd, char __user *name, void __user *value, size_t size)
{
struct file *f;
- ssize_t error = -EBADF;
+ ssize_t error;
+
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+ error = -EBADF;
f = fget(fd);
if (!f)
return error;
@@ -391,6 +417,10 @@ sys_listxattr(char __user *path, char __user *list, size_t size)
struct nameidata nd;
ssize_t error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk(path, &nd);
if (error)
return error;
@@ -405,6 +435,10 @@ sys_llistxattr(char __user *path, char __user *list, size_t size)
struct nameidata nd;
ssize_t error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk_link(path, &nd);
if (error)
return error;
@@ -417,8 +451,13 @@ asmlinkage ssize_t
sys_flistxattr(int fd, char __user *list, size_t size)
{
struct file *f;
- ssize_t error = -EBADF;
+ ssize_t error;
+
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+ error = -EBADF;
f = fget(fd);
if (!f)
return error;
@@ -452,6 +491,10 @@ sys_removexattr(char __user *path, char __user *name)
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk(path, &nd);
if (error)
return error;
@@ -466,6 +509,10 @@ sys_lremovexattr(char __user *path, char __user *name)
struct nameidata nd;
int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
error = user_path_walk_link(path, &nd);
if (error)
return error;
@@ -479,8 +526,13 @@ sys_fremovexattr(int fd, char __user *name)
{
struct file *f;
struct dentry *dentry;
- int error = -EBADF;
+ int error;
+
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+ error = -EBADF;
f = fget(fd);
if (!f)
return error;
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index 91c8c07..f20f057 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -39,6 +39,7 @@ struct linux_binprm{
int e_uid, e_gid;
kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
void *security;
+ struct cred *cred;
int argc, envc;
char * filename; /* Name of binary as seen by procps */
char * interp; /* Name of the binary really executed. Most
diff --git a/include/linux/cred.h b/include/linux/cred.h
new file mode 100644
index 0000000..f3d98a8
--- /dev/null
+++ b/include/linux/cred.h
@@ -0,0 +1,172 @@
+/* Credentials management
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#ifndef _LINUX_CRED_H
+#define _LINUX_CRED_H
+
+#include <linux/rcupdate.h>
+
+#ifdef __KERNEL__
+
+/*
+ * credentials record
+ * - COW semantics apply
+ */
+struct cred {
+ atomic_t usage;
+ uid_t uid; /* fsuid as was */
+ gid_t gid; /* fsgid as was */
+ struct rcu_head exterminate; /* cred destroyer */
+ struct group_info *group_info;
+
+ /* caches for references to the three task keyrings
+ * - note that key_ref_t isn't typedef'd at this point, hence the odd
+ * types
+ */
+#ifdef CONFIG_KEYS
+ struct __key_reference_with_attributes *session_keyring;
+ struct __key_reference_with_attributes *process_keyring;
+ struct __key_reference_with_attributes *thread_keyring;
+#endif
+};
+
+extern struct cred init_cred;
+
+extern int update_current_cred(void);
+extern void put_cred(struct cred *);
+extern void change_fsuid(struct cred *, uid_t);
+extern void change_fsgid(struct cred *, gid_t);
+extern void change_groups(struct cred *, struct group_info *);
+extern struct cred *dup_cred(const struct cred *);
+
+/**
+ * get_cred - Get an extra reference on a credentials record
+ * @cred: The credentials record to reference
+ *
+ * Get an extra reference on a credentials record. This must be released by
+ * calling put_cred().
+ */
+static inline struct cred *get_cred(struct cred *cred)
+{
+ atomic_inc(&cred->usage);
+ return cred;
+}
+
+/**
+ * get_current_cred - Get an extra reference on the current's credentials record
+ *
+ * Get an extra reference on the credentials record attached to the current
+ * task. This must be released by calling put_cred().
+ */
+#define get_current_cred() \
+ ({ get_cred(current->cred); })
+
+/**
+ * task_cred - Access the credentials of another task
+ * @tsk: The task to access
+ *
+ * Get a pointer to the credentials record of the given task. The caller must
+ * have done rcu_read_lock() first. The credentials record is can only be
+ * accessed as long as the RCU readlock is held by the caller. If the
+ * credentials are required for longer, then a reference should be obtained on
+ * the cred struct.
+ *
+ * This is not required for the a task to access its own credentials. Tasks
+ * may not alter the credentials of other tasks.
+ */
+#define task_cred(tsk) \
+ ({ rcu_dereference((tsk)->cred); })
+
+/**
+ * __task_fsuid - Get the FSUID of another task (caller holds RCU read lock)
+ * task_fsuid - Get the FSUID of another task
+ * @tsk: The task to access
+ *
+ * Get the active filesystem access UID of another task. __task_fsuid()
+ * requires the caller to hold the RCU read lock, task_fsuid() does not.
+ */
+#define __task_fsuid(tsk) (task_cred(tsk)->uid)
+#define task_fsuid(tsk) \
+({ \
+ uid_t ____x; \
+ rcu_read_lock(); \
+ ____x = __task_fsuid(tsk); \
+ rcu_read_unlock(); \
+ ____x; \
+})
+
+/**
+ * __task_fsgid - Get the FSGID of another task (caller holds RCU read lock)
+ * task_fsgid - Get the FSGID of another task
+ * @tsk: The task to access
+ *
+ * Get the active filesystem access GID of another task. __task_fsgid()
+ * requires the caller to hold the RCU read lock, task_fsgid() does not.
+ */
+#define __task_fsgid(tsk) (task_cred(tsk)->gid)
+#define task_fsgid(tsk) \
+({ \
+ gid_t ____x; \
+ rcu_read_lock(); \
+ ____x = __task_fsgid(tsk); \
+ rcu_read_unlock(); \
+ ____x; \
+})
+
+/**
+ * get_task_cred - Get an extra reference on a credentials record of a task
+ * @tsk: The task to look in
+ *
+ * Get an extra reference on a credentials record of the given task and return
+ * a pointer to it. This must be released by calling put_cred(). The caller
+ * must have done rcu_read_lock() first.
+ */
+#define get_task_cred(tsk) \
+ ({ get_cred(task_cred((tsk))); })
+
+/**
+ * __set_current_cred - Swap the current credentials on the current task
+ * @cred: The revised credentials
+ *
+ * Exchange the credential record of the current task for an updated one. This
+ * transfers a reference on the passed credential to the current task_struct,
+ * so the caller may need to get an extra reference first. The old credentials
+ * are returned and must be disposed of appropriately.
+ *
+ * Write-locking is achieved by the fact that a thread's credentials may only
+ * be changed by that thread itself, so no explicit locking is required.
+ */
+#define __set_current_cred(CRED) \
+({ \
+ struct cred *___old = current->cred; \
+ rcu_assign_pointer(current->cred, (CRED)); \
+ ___old; \
+})
+
+/**
+ * set_current_cred - Change the current credentials on the current task
+ * @cred: The revised credentials
+ *
+ * Exchange the credential record of the current task for an updated one. This
+ * transfers a reference on the passed credential to the current task_struct,
+ * so the caller may need to get an extra reference first. The old credentials
+ * are released.
+ *
+ * Write-locking is achieved by the fact that a thread's credentials may only
+ * be changed by that thread itself, so no explicit locking is required.
+ */
+#define set_current_cred(CRED) \
+do { \
+ put_cred(__set_current_cred(CRED)); \
+} while (0)
+
+#endif /* __KERNEL__ */
+#endif /* _LINUX_CRED_H */
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 16421f6..bf35441 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -285,6 +285,7 @@ extern int dir_notify_enable;
#include <linux/mutex.h>
#include <linux/sysctl.h>
#include <linux/capability.h>
+#include <linux/cred.h>
#include <asm/atomic.h>
#include <asm/semaphore.h>
@@ -736,7 +737,7 @@ struct file {
mode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
- unsigned int f_uid, f_gid;
+ struct cred *f_cred;
struct file_ra_state f_ra;
unsigned long f_version;
@@ -999,7 +1000,7 @@ enum {
#define has_fs_excl() atomic_read(¤t->fs_excl)
#define is_owner_or_cap(inode) \
- ((current->fsuid == (inode)->i_uid) || capable(CAP_FOWNER))
+ ((current->cred->uid == (inode)->i_uid) || capable(CAP_FOWNER))
/* not quite ready to be deprecated, but... */
extern void lock_super(struct super_block *);
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index f8abfa3..5cb7931 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -89,8 +89,6 @@ extern struct nsproxy init_nsproxy;
.signalfd_wqh = __WAIT_QUEUE_HEAD_INITIALIZER(sighand.signalfd_wqh), \
}
-extern struct group_info init_groups;
-
#define INIT_STRUCT_PID { \
.count = ATOMIC_INIT(1), \
.nr = 0, \
@@ -142,7 +140,7 @@ extern struct group_info init_groups;
.children = LIST_HEAD_INIT(tsk.children), \
.sibling = LIST_HEAD_INIT(tsk.sibling), \
.group_leader = &tsk, \
- .group_info = &init_groups, \
+ .cred = &init_cred, \
.cap_effective = CAP_INIT_EFF_SET, \
.cap_inheritable = CAP_INIT_INH_SET, \
.cap_permitted = CAP_FULL_SET, \
diff --git a/include/linux/sched.h b/include/linux/sched.h
index a01ac6d..ca0d553 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -79,6 +79,7 @@ struct sched_param {
#include <linux/rcupdate.h>
#include <linux/futex.h>
#include <linux/rtmutex.h>
+#include <linux/cred.h>
#include <linux/time.h>
#include <linux/param.h>
@@ -1033,9 +1034,9 @@ struct task_struct {
struct list_head cpu_timers[3];
/* process credentials */
- uid_t uid,euid,suid,fsuid;
- gid_t gid,egid,sgid,fsgid;
- struct group_info *group_info;
+ struct cred *cred;
+ uid_t uid,euid,suid;
+ gid_t gid,egid,sgid;
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
unsigned keep_capabilities:1;
struct user_struct *user;
diff --git a/include/linux/sunrpc/auth.h b/include/linux/sunrpc/auth.h
index 7a69ca3..c06891d 100644
--- a/include/linux/sunrpc/auth.h
+++ b/include/linux/sunrpc/auth.h
@@ -21,13 +21,6 @@
/* size of the nodename buffer */
#define UNX_MAXNODENAME 32
-/* Work around the lack of a VFS credential */
-struct auth_cred {
- uid_t uid;
- gid_t gid;
- struct group_info *group_info;
-};
-
/*
* Client user credentials
*/
@@ -103,8 +96,8 @@ struct rpc_authops {
struct rpc_auth * (*create)(struct rpc_clnt *, rpc_authflavor_t);
void (*destroy)(struct rpc_auth *);
- struct rpc_cred * (*lookup_cred)(struct rpc_auth *, struct auth_cred *, int);
- struct rpc_cred * (*crcreate)(struct rpc_auth*, struct auth_cred *, int);
+ struct rpc_cred * (*lookup_cred)(struct rpc_auth *, struct cred *, int);
+ struct rpc_cred * (*crcreate)(struct rpc_auth*, struct cred *, int);
};
struct rpc_credops {
@@ -112,7 +105,7 @@ struct rpc_credops {
int (*cr_init)(struct rpc_auth *, struct rpc_cred *);
void (*crdestroy)(struct rpc_cred *);
- int (*crmatch)(struct auth_cred *, struct rpc_cred *, int);
+ int (*crmatch)(struct cred *, struct rpc_cred *, int);
__be32 * (*crmarshal)(struct rpc_task *, __be32 *);
int (*crrefresh)(struct rpc_task *);
__be32 * (*crvalidate)(struct rpc_task *, __be32 *);
@@ -133,8 +126,8 @@ int rpcauth_register(const struct rpc_authops *);
int rpcauth_unregister(const struct rpc_authops *);
struct rpc_auth * rpcauth_create(rpc_authflavor_t, struct rpc_clnt *);
void rpcauth_release(struct rpc_auth *);
-struct rpc_cred * rpcauth_lookup_credcache(struct rpc_auth *, struct auth_cred *, int);
-void rpcauth_init_cred(struct rpc_cred *, const struct auth_cred *, struct rpc_auth *, const struct rpc_credops *);
+struct rpc_cred * rpcauth_lookup_credcache(struct rpc_auth *, struct cred *, int);
+void rpcauth_init_cred(struct rpc_cred *, const struct cred *, struct rpc_auth *, const struct rpc_credops *);
struct rpc_cred * rpcauth_lookupcred(struct rpc_auth *, int);
struct rpc_cred * rpcauth_bindcred(struct rpc_task *);
void rpcauth_holdcred(struct rpc_task *);
diff --git a/kernel/Makefile b/kernel/Makefile
index 2a99983..1f1f17b 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -9,7 +9,7 @@ obj-y = sched.o fork.o exec_domain.o panic.o printk.o profile.o \
rcupdate.o extable.o params.o posix-timers.o \
kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o mutex.o \
hrtimer.o rwsem.o latency.o nsproxy.o srcu.o die_notifier.o \
- utsname.o
+ utsname.o cred.o
obj-$(CONFIG_STACKTRACE) += stacktrace.o
obj-y += time/
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 04f3ffb..282e041 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -303,7 +303,8 @@ static int audit_filter_rules(struct task_struct *tsk,
result = audit_comparator(tsk->suid, f->op, f->val);
break;
case AUDIT_FSUID:
- result = audit_comparator(tsk->fsuid, f->op, f->val);
+ result = audit_comparator(task_fsuid(tsk), f->op,
+ f->val);
break;
case AUDIT_GID:
result = audit_comparator(tsk->gid, f->op, f->val);
@@ -315,7 +316,8 @@ static int audit_filter_rules(struct task_struct *tsk,
result = audit_comparator(tsk->sgid, f->op, f->val);
break;
case AUDIT_FSGID:
- result = audit_comparator(tsk->fsgid, f->op, f->val);
+ result = audit_comparator(task_fsgid(tsk), f->op,
+ f->val);
break;
case AUDIT_PERS:
result = audit_comparator(tsk->personality, f->op, f->val);
@@ -885,12 +887,15 @@ static void audit_log_exit(struct audit_context *context, struct task_struct *ts
context->gid = tsk->gid;
context->euid = tsk->euid;
context->suid = tsk->suid;
- context->fsuid = tsk->fsuid;
context->egid = tsk->egid;
context->sgid = tsk->sgid;
- context->fsgid = tsk->fsgid;
context->personality = tsk->personality;
+ rcu_read_lock();
+ context->fsuid = __task_fsuid(tsk);
+ context->fsgid = __task_fsgid(tsk);
+ rcu_read_unlock();
+
ab = audit_log_start(context, GFP_KERNEL, AUDIT_SYSCALL);
if (!ab)
return; /* audit_panic has been called */
diff --git a/kernel/cred.c b/kernel/cred.c
new file mode 100644
index 0000000..4710b60
--- /dev/null
+++ b/kernel/cred.c
@@ -0,0 +1,179 @@
+/* Credential caching/management
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/security.h>
+#include <linux/key.h>
+#include "kernel-int.h"
+
+/*
+ * the credentials for the init task
+ * - the usage count is elevated so that this is never freed
+ */
+struct cred init_cred = {
+ .usage = ATOMIC_INIT(2),
+ .group_info = &init_groups,
+};
+
+/**
+ * update_current_cred - Bring the current task's creds up to date
+ *
+ * Bring the current task's credentials up to date with respect to the keyrings
+ * they shadow. The process and session level keyrings may get changed by
+ * sibling threads with the same process, but the change can't be applied back
+ * to this thread's cred struct except by this thread itself.
+ */
+int update_current_cred(void)
+{
+ struct signal_struct *sig = current->signal;
+ struct cred *cred = current->cred;
+ struct key *keyring;
+
+ if (
+#ifdef CONFIG_KEYS
+ key_ref_to_ptr(cred->session_keyring) == sig->session_keyring &&
+ key_ref_to_ptr(cred->process_keyring) == sig->process_keyring &&
+ key_ref_to_ptr(cred->thread_keyring) == current->thread_keyring &&
+#endif
+ true)
+ return 0;
+
+ cred = kmalloc(sizeof(struct cred), GFP_KERNEL);
+ if (!cred)
+ return -ENOMEM;
+
+ *cred = *current->cred;
+ atomic_set(&cred->usage, 1);
+ get_group_info(cred->group_info);
+
+#ifdef CONFIG_KEYS
+ rcu_read_lock();
+ keyring = key_get(rcu_dereference(sig->session_keyring));
+ rcu_read_unlock();
+ if (!keyring)
+ keyring = key_get(current->user->session_keyring);
+ cred->session_keyring = make_key_ref(keyring, 1);
+
+ keyring = key_get(sig->process_keyring);
+ if (keyring)
+ cred->process_keyring = make_key_ref(keyring, 1);
+ else
+ cred->process_keyring = NULL;
+
+ cred->thread_keyring = NULL;
+#endif
+
+ set_current_cred(cred);
+ return 0;
+}
+
+/**
+ * dup_cred - Duplicate a credentials structure
+ * @pcred: The credentials record to duplicate
+ *
+ * Duplicate and return a credentials structure so that the copy can be
+ * modified. NULL is returned if there is insufficient memory to make the
+ * copy.
+ */
+struct cred *dup_cred(const struct cred *pcred)
+{
+ struct cred *cred;
+
+ cred = kmalloc(sizeof(struct cred), GFP_KERNEL);
+ if (likely(cred)) {
+ *cred = *pcred;
+ atomic_set(&cred->usage, 1);
+ get_group_info(cred->group_info);
+ key_get(key_ref_to_ptr(cred->session_keyring));
+ key_get(key_ref_to_ptr(cred->process_keyring));
+ key_get(key_ref_to_ptr(cred->thread_keyring));
+ }
+ return cred;
+}
+
+EXPORT_SYMBOL(dup_cred);
+
+/*
+ * RCU-based credentials destroyer
+ */
+static void put_cred_rcu(struct rcu_head *rcu)
+{
+ struct cred *cred = container_of(rcu, struct cred, exterminate);
+
+ put_group_info(cred->group_info);
+ key_ref_put(cred->session_keyring);
+ key_ref_put(cred->process_keyring);
+ key_ref_put(cred->thread_keyring);
+ kfree(cred);
+}
+
+/**
+ * put_cred - Release a reference to a credentials record
+ * cred: The credentials record to release
+ *
+ * Release a reference to a credentials record. When the last reference is
+ * released, the record will be deleted with due care for RCU accesses still
+ * ongoing.
+ */
+void put_cred(struct cred *cred)
+{
+ if (atomic_dec_and_test(&cred->usage))
+ call_rcu(&cred->exterminate, put_cred_rcu);
+}
+
+EXPORT_SYMBOL(put_cred);
+
+/**
+ * change_fsuid - Change the VFS applicable UID in a new credential record
+ * @cred: The credential record to alter
+ * @uid: The user ID to set
+ *
+ * Change the VFS access and creation user ID in a new credential record.
+ */
+void change_fsuid(struct cred *cred, uid_t uid)
+{
+ cred->uid = uid;
+}
+
+EXPORT_SYMBOL(change_fsuid);
+
+/**
+ * change_fsgid - Change the VFS applicable GID in a new credential record
+ * @cred: The credential record to alter
+ * @gid: The group ID to set
+ *
+ * Change the VFS access and creation group ID in a new credential record.
+ */
+void change_fsgid(struct cred *cred, gid_t gid)
+{
+ cred->gid = gid;
+}
+
+EXPORT_SYMBOL(change_fsgid);
+
+/**
+ * change_groups - Change the supplementary groups in a new credential record
+ * @cred: The credential record to alter
+ * @group_info: The supplementary groups to attach
+ *
+ * Change the VFS access supplementary group list in a new credential record.
+ */
+void change_groups(struct cred *cred, struct group_info *group_info)
+{
+ struct group_info *old = cred->group_info;
+
+ get_group_info(group_info);
+ cred->group_info = group_info;
+ put_group_info(old);
+}
+
+EXPORT_SYMBOL(change_groups);
diff --git a/kernel/exit.c b/kernel/exit.c
index 993369e..c366ae7 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -288,6 +288,7 @@ static void reparent_to_kthreadd(void)
/* cpus_allowed? */
/* rt_priority? */
/* signals? */
+ set_current_cred(get_cred(&init_cred));
security_task_reparent_to_init(current);
memcpy(current->signal->rlim, init_task.signal->rlim,
sizeof(current->signal->rlim));
diff --git a/kernel/fork.c b/kernel/fork.c
index 33f12f4..677c353 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -121,7 +121,7 @@ void __put_task_struct(struct task_struct *tsk)
security_task_free(tsk);
free_uid(tsk->user);
- put_group_info(tsk->group_info);
+ put_cred(tsk->cred);
delayacct_tsk_free(tsk);
if (!profile_handoff_task(tsk))
@@ -949,6 +949,57 @@ static inline void rt_mutex_init_task(struct task_struct *p)
}
/*
+ * Copy a set of credentials
+ */
+static int copy_cred(struct task_struct *p)
+{
+ struct cred *cred;
+#ifdef CONFIG_KEYS
+ struct key *session_keyring, *process_keyring;
+#endif
+
+ /* share the credentials if we can */
+ if (
+#ifdef CONFIG_KEYS
+ !p->cred->thread_keyring &&
+#endif
+ true
+ ) {
+ atomic_inc(&p->cred->usage);
+ } else {
+ cred = kmalloc(sizeof(struct cred), GFP_KERNEL);
+ if (!cred)
+ return -ENOMEM;
+
+ *cred = *p->cred;
+ atomic_set(&cred->usage, 1);
+ get_group_info(cred->group_info);
+
+#ifdef CONFIG_KEYS
+ rcu_read_lock();
+ session_keyring =
+ key_get(rcu_dereference(p->signal->session_keyring));
+ rcu_read_unlock();
+ if (!session_keyring)
+ session_keyring = key_get(p->user->session_keyring);
+ cred->session_keyring = make_key_ref(session_keyring, 1);
+
+ process_keyring = key_get(p->signal->process_keyring);
+ if (process_keyring)
+ cred->process_keyring =
+ make_key_ref(process_keyring, 1);
+ else
+ cred->process_keyring = NULL;
+
+ cred->thread_keyring = NULL;
+#endif
+
+ p->cred = cred;
+ }
+ return 0;
+}
+
+/*
* This creates a new process as a copy of the old one,
* but does not actually start it yet.
*
@@ -1010,7 +1061,8 @@ static struct task_struct *copy_process(unsigned long clone_flags,
atomic_inc(&p->user->__count);
atomic_inc(&p->user->processes);
- get_group_info(p->group_info);
+ if ((retval = copy_cred(p) < 0))
+ goto bad_fork_cleanup_count;
/*
* If multiple threads are within copy_process(), then this check
@@ -1018,10 +1070,10 @@ static struct task_struct *copy_process(unsigned long clone_flags,
* to stop root fork bombs.
*/
if (nr_threads >= max_threads)
- goto bad_fork_cleanup_count;
+ goto bad_fork_cleanup_cred;
if (!try_module_get(task_thread_info(p)->exec_domain->module))
- goto bad_fork_cleanup_count;
+ goto bad_fork_cleanup_cred;
if (p->binfmt && !try_module_get(p->binfmt->module))
goto bad_fork_cleanup_put_domain;
@@ -1309,8 +1361,9 @@ bad_fork_cleanup_delays_binfmt:
module_put(p->binfmt->module);
bad_fork_cleanup_put_domain:
module_put(task_thread_info(p)->exec_domain->module);
+bad_fork_cleanup_cred:
+ put_cred(p->cred);
bad_fork_cleanup_count:
- put_group_info(p->group_info);
atomic_dec(&p->user->processes);
free_uid(p->user);
bad_fork_free:
diff --git a/kernel/kernel-int.h b/kernel/kernel-int.h
new file mode 100644
index 0000000..16c68e3
--- /dev/null
+++ b/kernel/kernel-int.h
@@ -0,0 +1,15 @@
+/* kernel/ internal definitions
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+/*
+ * sys.c
+ */
+extern struct group_info init_groups;
diff --git a/kernel/sys.c b/kernel/sys.c
index 1b33b05..9bb591f 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -41,6 +41,7 @@
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/unistd.h>
+#include "kernel-int.h"
#ifndef SET_UNALIGN_CTL
# define SET_UNALIGN_CTL(a,b) (-EINVAL)
@@ -1011,6 +1012,7 @@ void ctrl_alt_del(void)
*/
asmlinkage long sys_setregid(gid_t rgid, gid_t egid)
{
+ struct cred *cred;
int old_rgid = current->gid;
int old_egid = current->egid;
int new_rgid = old_rgid;
@@ -1038,6 +1040,11 @@ asmlinkage long sys_setregid(gid_t rgid, gid_t egid)
else
return -EPERM;
}
+
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+
if (new_egid != old_egid) {
set_dumpable(current->mm, suid_dumpable);
smp_wmb();
@@ -1045,9 +1052,10 @@ asmlinkage long sys_setregid(gid_t rgid, gid_t egid)
if (rgid != (gid_t) -1 ||
(egid != (gid_t) -1 && egid != old_rgid))
current->sgid = new_egid;
- current->fsgid = new_egid;
current->egid = new_egid;
current->gid = new_rgid;
+ change_fsgid(cred, new_egid);
+ set_current_cred(cred);
key_fsgid_changed(current);
proc_id_connector(current, PROC_EVENT_GID);
return 0;
@@ -1060,6 +1068,7 @@ asmlinkage long sys_setregid(gid_t rgid, gid_t egid)
*/
asmlinkage long sys_setgid(gid_t gid)
{
+ struct cred *cred;
int old_egid = current->egid;
int retval;
@@ -1067,22 +1076,29 @@ asmlinkage long sys_setgid(gid_t gid)
if (retval)
return retval;
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+
if (capable(CAP_SETGID)) {
if (old_egid != gid) {
set_dumpable(current->mm, suid_dumpable);
smp_wmb();
}
- current->gid = current->egid = current->sgid = current->fsgid = gid;
+ current->gid = current->egid = current->sgid = gid;
} else if ((gid == current->gid) || (gid == current->sgid)) {
if (old_egid != gid) {
set_dumpable(current->mm, suid_dumpable);
smp_wmb();
}
- current->egid = current->fsgid = gid;
- }
- else
+ current->egid = gid;
+ } else {
+ put_cred(cred);
return -EPERM;
+ }
+ change_fsgid(cred, gid);
+ set_current_cred(cred);
key_fsgid_changed(current);
proc_id_connector(current, PROC_EVENT_GID);
return 0;
@@ -1130,6 +1146,7 @@ static int set_user(uid_t new_ruid, int dumpclear)
*/
asmlinkage long sys_setreuid(uid_t ruid, uid_t euid)
{
+ struct cred *cred;
int old_ruid, old_euid, old_suid, new_ruid, new_euid;
int retval;
@@ -1158,19 +1175,26 @@ asmlinkage long sys_setreuid(uid_t ruid, uid_t euid)
return -EPERM;
}
- if (new_ruid != old_ruid && set_user(new_ruid, new_euid != old_euid) < 0)
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+
+ if (new_ruid != old_ruid && set_user(new_ruid, new_euid != old_euid) < 0) {
+ put_cred(cred);
return -EAGAIN;
+ }
if (new_euid != old_euid) {
set_dumpable(current->mm, suid_dumpable);
smp_wmb();
}
- current->fsuid = current->euid = new_euid;
+ current->euid = new_euid;
if (ruid != (uid_t) -1 ||
(euid != (uid_t) -1 && euid != old_ruid))
current->suid = current->euid;
- current->fsuid = current->euid;
+ change_fsuid(cred, new_euid);
+ set_current_cred(cred);
key_fsuid_changed(current);
proc_id_connector(current, PROC_EVENT_UID);
@@ -1192,6 +1216,7 @@ asmlinkage long sys_setreuid(uid_t ruid, uid_t euid)
*/
asmlinkage long sys_setuid(uid_t uid)
{
+ struct cred *cred;
int old_euid = current->euid;
int old_ruid, old_suid, new_suid;
int retval;
@@ -1200,24 +1225,33 @@ asmlinkage long sys_setuid(uid_t uid)
if (retval)
return retval;
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+
old_ruid = current->uid;
old_suid = current->suid;
new_suid = old_suid;
if (capable(CAP_SETUID)) {
- if (uid != old_ruid && set_user(uid, old_euid != uid) < 0)
+ if (uid != old_ruid && set_user(uid, old_euid != uid) < 0) {
+ put_cred(cred);
return -EAGAIN;
+ }
new_suid = uid;
- } else if ((uid != current->uid) && (uid != new_suid))
+ } else if ((uid != current->uid) && (uid != new_suid)) {
+ put_cred(cred);
return -EPERM;
+ }
if (old_euid != uid) {
set_dumpable(current->mm, suid_dumpable);
smp_wmb();
}
- current->fsuid = current->euid = uid;
+ current->euid = uid;
current->suid = new_suid;
-
+ change_fsuid(cred, uid);
+ set_current_cred(cred);
key_fsuid_changed(current);
proc_id_connector(current, PROC_EVENT_UID);
@@ -1231,6 +1265,7 @@ asmlinkage long sys_setuid(uid_t uid)
*/
asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
{
+ struct cred *cred;
int old_ruid = current->uid;
int old_euid = current->euid;
int old_suid = current->suid;
@@ -1251,9 +1286,16 @@ asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
(suid != current->euid) && (suid != current->suid))
return -EPERM;
}
+
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+
if (ruid != (uid_t) -1) {
- if (ruid != current->uid && set_user(ruid, euid != current->euid) < 0)
+ if (ruid != current->uid && set_user(ruid, euid != current->euid) < 0) {
+ put_cred(cred);
return -EAGAIN;
+ }
}
if (euid != (uid_t) -1) {
if (euid != current->euid) {
@@ -1262,10 +1304,10 @@ asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
}
current->euid = euid;
}
- current->fsuid = current->euid;
if (suid != (uid_t) -1)
current->suid = suid;
-
+ change_fsuid(cred, current->euid);
+ set_current_cred(cred);
key_fsuid_changed(current);
proc_id_connector(current, PROC_EVENT_UID);
@@ -1288,6 +1330,7 @@ asmlinkage long sys_getresuid(uid_t __user *ruid, uid_t __user *euid, uid_t __us
*/
asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
{
+ struct cred *cred;
int retval;
retval = security_task_setgid(rgid, egid, sgid, LSM_SETID_RES);
@@ -1305,6 +1348,11 @@ asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
(sgid != current->egid) && (sgid != current->sgid))
return -EPERM;
}
+
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+
if (egid != (gid_t) -1) {
if (egid != current->egid) {
set_dumpable(current->mm, suid_dumpable);
@@ -1312,12 +1360,13 @@ asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
}
current->egid = egid;
}
- current->fsgid = current->egid;
if (rgid != (gid_t) -1)
current->gid = rgid;
if (sgid != (gid_t) -1)
current->sgid = sgid;
+ change_fsgid(cred, current->egid);
+ set_current_cred(cred);
key_fsgid_changed(current);
proc_id_connector(current, PROC_EVENT_GID);
return 0;
@@ -1343,23 +1392,31 @@ asmlinkage long sys_getresgid(gid_t __user *rgid, gid_t __user *egid, gid_t __us
*/
asmlinkage long sys_setfsuid(uid_t uid)
{
+ struct cred *cred;
int old_fsuid;
- old_fsuid = current->fsuid;
+ old_fsuid = current->cred->uid;
if (security_task_setuid(uid, (uid_t)-1, (uid_t)-1, LSM_SETID_FS))
return old_fsuid;
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+
if (uid == current->uid || uid == current->euid ||
- uid == current->suid || uid == current->fsuid ||
+ uid == current->suid || uid == current->cred->uid ||
capable(CAP_SETUID)) {
if (uid != old_fsuid) {
set_dumpable(current->mm, suid_dumpable);
smp_wmb();
}
- current->fsuid = uid;
+ change_fsuid(cred, uid);
+ set_current_cred(cred);
+ key_fsuid_changed(current);
+ } else {
+ put_cred(cred);
}
- key_fsuid_changed(current);
proc_id_connector(current, PROC_EVENT_UID);
security_task_post_setuid(old_fsuid, (uid_t)-1, (uid_t)-1, LSM_SETID_FS);
@@ -1372,22 +1429,30 @@ asmlinkage long sys_setfsuid(uid_t uid)
*/
asmlinkage long sys_setfsgid(gid_t gid)
{
+ struct cred *cred;
int old_fsgid;
- old_fsgid = current->fsgid;
+ old_fsgid = current->cred->gid;
if (security_task_setgid(gid, (gid_t)-1, (gid_t)-1, LSM_SETID_FS))
return old_fsgid;
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+
if (gid == current->gid || gid == current->egid ||
- gid == current->sgid || gid == current->fsgid ||
+ gid == current->sgid || gid == current->cred->gid ||
capable(CAP_SETGID)) {
if (gid != old_fsgid) {
set_dumpable(current->mm, suid_dumpable);
smp_wmb();
}
- current->fsgid = gid;
+ change_fsgid(cred, gid);
+ set_current_cred(cred);
key_fsgid_changed(current);
proc_id_connector(current, PROC_EVENT_GID);
+ } else {
+ put_cred(cred);
}
return old_fsgid;
}
@@ -1754,23 +1819,20 @@ int groups_search(struct group_info *group_info, gid_t grp)
/* validate and set current->group_info */
int set_current_groups(struct group_info *group_info)
{
+ struct cred *cred;
int retval;
- struct group_info *old_info;
retval = security_task_setgroups(group_info);
if (retval)
return retval;
- groups_sort(group_info);
- get_group_info(group_info);
-
- task_lock(current);
- old_info = current->group_info;
- current->group_info = group_info;
- task_unlock(current);
-
- put_group_info(old_info);
+ cred = dup_cred(current->cred);
+ if (!cred)
+ return -ENOMEM;
+ groups_sort(group_info);
+ change_groups(cred, group_info);
+ set_current_cred(cred);
return 0;
}
@@ -1778,24 +1840,24 @@ EXPORT_SYMBOL(set_current_groups);
asmlinkage long sys_getgroups(int gidsetsize, gid_t __user *grouplist)
{
+ struct group_info *group_info = current->cred->group_info;
int i = 0;
/*
- * SMP: Nobody else can change our grouplist. Thus we are
- * safe.
+ * SMP: Nobody else can change our credentials. Thus we are safe.
*/
if (gidsetsize < 0)
return -EINVAL;
/* no need to grab task_lock here; it cannot change */
- i = current->group_info->ngroups;
+ i = group_info->ngroups;
if (gidsetsize) {
if (i > gidsetsize) {
i = -EINVAL;
goto out;
}
- if (groups_to_user(grouplist, current->group_info)) {
+ if (groups_to_user(grouplist, group_info)) {
i = -EFAULT;
goto out;
}
@@ -1839,9 +1901,11 @@ asmlinkage long sys_setgroups(int gidsetsize, gid_t __user *grouplist)
*/
int in_group_p(gid_t grp)
{
+ struct cred *cred = current->cred;
int retval = 1;
- if (grp != current->fsgid)
- retval = groups_search(current->group_info, grp);
+
+ if (grp != cred->gid)
+ retval = groups_search(cred->group_info, grp);
return retval;
}
@@ -1851,7 +1915,7 @@ int in_egroup_p(gid_t grp)
{
int retval = 1;
if (grp != current->egid)
- retval = groups_search(current->group_info, grp);
+ retval = groups_search(current->cred->group_info, grp);
return retval;
}
diff --git a/kernel/uid16.c b/kernel/uid16.c
index dd308ba..5a8b95e 100644
--- a/kernel/uid16.c
+++ b/kernel/uid16.c
@@ -161,25 +161,24 @@ static int groups16_from_user(struct group_info *group_info,
asmlinkage long sys_getgroups16(int gidsetsize, old_gid_t __user *grouplist)
{
+ struct group_info *group_info = current->cred->group_info;
int i = 0;
if (gidsetsize < 0)
return -EINVAL;
- get_group_info(current->group_info);
- i = current->group_info->ngroups;
+ i = group_info->ngroups;
if (gidsetsize) {
if (i > gidsetsize) {
i = -EINVAL;
goto out;
}
- if (groups16_to_user(grouplist, current->group_info)) {
+ if (groups16_to_user(grouplist, group_info)) {
i = -EFAULT;
goto out;
}
}
out:
- put_group_info(current->group_info);
return i;
}
diff --git a/mm/fadvise.c b/mm/fadvise.c
index 0df4c89..db3d602 100644
--- a/mm/fadvise.c
+++ b/mm/fadvise.c
@@ -35,6 +35,10 @@ asmlinkage long sys_fadvise64_64(int fd, loff_t offset, loff_t len, int advice)
unsigned long nrpages;
int ret = 0;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
if (!file)
return -EBADF;
diff --git a/mm/filemap.c b/mm/filemap.c
index 90b657b..3257be2 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1235,6 +1235,10 @@ asmlinkage ssize_t sys_readahead(int fd, loff_t offset, size_t count)
ssize_t ret;
struct file *file;
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
ret = -EBADF;
file = fget(fd);
if (file) {
diff --git a/mm/fremap.c b/mm/fremap.c
index c395b1a..a1a2088 100644
--- a/mm/fremap.c
+++ b/mm/fremap.c
@@ -122,9 +122,14 @@ asmlinkage long sys_remap_file_pages(unsigned long start, unsigned long size,
struct address_space *mapping;
unsigned long end = start + size;
struct vm_area_struct *vma;
- int err = -EINVAL;
+ int err;
int has_write_lock = 0;
+ err = update_current_cred();
+ if (err < 0)
+ return err;
+
+ err = -EINVAL;
if (__prot)
return err;
/*
diff --git a/mm/madvise.c b/mm/madvise.c
index 93ee375..508a584 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -286,10 +286,15 @@ asmlinkage long sys_madvise(unsigned long start, size_t len_in, int behavior)
unsigned long end, tmp;
struct vm_area_struct * vma, *prev;
int unmapped_error = 0;
- int error = -EINVAL;
+ int error;
int write;
size_t len;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
+ error = -EINVAL;
write = madvise_need_mmap_write(behavior);
if (write)
down_write(¤t->mm->mmap_sem);
diff --git a/mm/msync.c b/mm/msync.c
index 144a757..f8c1b3b 100644
--- a/mm/msync.c
+++ b/mm/msync.c
@@ -34,8 +34,13 @@ asmlinkage long sys_msync(unsigned long start, size_t len, int flags)
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
int unmapped_error = 0;
- int error = -EINVAL;
+ int error;
+ error = update_current_cred();
+ if (error < 0)
+ return error;
+
+ error = -EINVAL;
if (flags & ~(MS_ASYNC | MS_INVALIDATE | MS_SYNC))
goto out;
if (start & ~PAGE_MASK)
diff --git a/mm/shmem.c b/mm/shmem.c
index fcd19d3..40e835b 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1394,8 +1394,6 @@ shmem_get_inode(struct super_block *sb, int mode, dev_t dev)
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_mapping->a_ops = &shmem_aops;
inode->i_mapping->backing_dev_info = &shmem_backing_dev_info;
@@ -2212,8 +2210,8 @@ static int shmem_fill_super(struct super_block *sb,
struct inode *inode;
struct dentry *root;
int mode = S_IRWXUGO | S_ISVTX;
- uid_t uid = current->fsuid;
- gid_t gid = current->fsgid;
+ uid_t uid = current->cred->uid;
+ gid_t gid = current->cred->gid;
int err = -ENOMEM;
struct shmem_sb_info *sbinfo;
unsigned long blocks = 0;
diff --git a/net/socket.c b/net/socket.c
index 7d44453..50bfeef 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -483,8 +483,6 @@ static struct socket *sock_alloc(void)
sock = SOCKET_I(inode);
inode->i_mode = S_IFSOCK | S_IRWXUGO;
- inode->i_uid = current->fsuid;
- inode->i_gid = current->fsgid;
get_cpu_var(sockets_in_use)++;
put_cpu_var(sockets_in_use);
diff --git a/net/sunrpc/auth.c b/net/sunrpc/auth.c
index 1ea2755..362a0de 100644
--- a/net/sunrpc/auth.c
+++ b/net/sunrpc/auth.c
@@ -267,7 +267,7 @@ rpcauth_cache_shrinker(int nr_to_scan, gfp_t gfp_mask)
* Look up a process' credentials in the authentication cache
*/
struct rpc_cred *
-rpcauth_lookup_credcache(struct rpc_auth *auth, struct auth_cred * acred,
+rpcauth_lookup_credcache(struct rpc_auth *auth, struct cred *acred,
int flags)
{
LIST_HEAD(free);
@@ -336,23 +336,13 @@ out:
struct rpc_cred *
rpcauth_lookupcred(struct rpc_auth *auth, int flags)
{
- struct auth_cred acred = {
- .uid = current->fsuid,
- .gid = current->fsgid,
- .group_info = current->group_info,
- };
- struct rpc_cred *ret;
-
dprintk("RPC: looking up %s cred\n",
auth->au_ops->au_name);
- get_group_info(acred.group_info);
- ret = auth->au_ops->lookup_cred(auth, &acred, flags);
- put_group_info(acred.group_info);
- return ret;
+ return auth->au_ops->lookup_cred(auth, current->cred, flags);
}
void
-rpcauth_init_cred(struct rpc_cred *cred, const struct auth_cred *acred,
+rpcauth_init_cred(struct rpc_cred *cred, const struct cred *acred,
struct rpc_auth *auth, const struct rpc_credops *ops)
{
INIT_HLIST_NODE(&cred->cr_hash);
@@ -372,25 +362,18 @@ struct rpc_cred *
rpcauth_bindcred(struct rpc_task *task)
{
struct rpc_auth *auth = task->tk_client->cl_auth;
- struct auth_cred acred = {
- .uid = current->fsuid,
- .gid = current->fsgid,
- .group_info = current->group_info,
- };
struct rpc_cred *ret;
int flags = 0;
dprintk("RPC: %5u looking up %s cred\n",
task->tk_pid, task->tk_client->cl_auth->au_ops->au_name);
- get_group_info(acred.group_info);
if (task->tk_flags & RPC_TASK_ROOTCREDS)
flags |= RPCAUTH_LOOKUP_ROOTCREDS;
- ret = auth->au_ops->lookup_cred(auth, &acred, flags);
+ ret = auth->au_ops->lookup_cred(auth, current->cred, flags);
if (!IS_ERR(ret))
task->tk_msg.rpc_cred = ret;
else
task->tk_status = PTR_ERR(ret);
- put_group_info(acred.group_info);
return ret;
}
diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c
index 53995af..bad2698 100644
--- a/net/sunrpc/auth_gss/auth_gss.c
+++ b/net/sunrpc/auth_gss/auth_gss.c
@@ -793,13 +793,13 @@ gss_destroy_cred(struct rpc_cred *cred)
* Lookup RPCSEC_GSS cred for the current process
*/
static struct rpc_cred *
-gss_lookup_cred(struct rpc_auth *auth, struct auth_cred *acred, int flags)
+gss_lookup_cred(struct rpc_auth *auth, struct cred *acred, int flags)
{
return rpcauth_lookup_credcache(auth, acred, flags);
}
static struct rpc_cred *
-gss_create_cred(struct rpc_auth *auth, struct auth_cred *acred, int flags)
+gss_create_cred(struct rpc_auth *auth, struct cred *acred, int flags)
{
struct gss_auth *gss_auth = container_of(auth, struct gss_auth, rpc_auth);
struct gss_cred *cred = NULL;
@@ -840,7 +840,7 @@ gss_cred_init(struct rpc_auth *auth, struct rpc_cred *cred)
}
static int
-gss_match(struct auth_cred *acred, struct rpc_cred *rc, int flags)
+gss_match(struct cred *acred, struct rpc_cred *rc, int flags)
{
struct gss_cred *gss_cred = container_of(rc, struct gss_cred, gc_base);
diff --git a/net/sunrpc/auth_null.c b/net/sunrpc/auth_null.c
index 537d0e8..c2fcefa 100644
--- a/net/sunrpc/auth_null.c
+++ b/net/sunrpc/auth_null.c
@@ -34,7 +34,7 @@ nul_destroy(struct rpc_auth *auth)
* Lookup NULL creds for current process
*/
static struct rpc_cred *
-nul_lookup_cred(struct rpc_auth *auth, struct auth_cred *acred, int flags)
+nul_lookup_cred(struct rpc_auth *auth, struct cred *acred, int flags)
{
return get_rpccred(&null_cred);
}
@@ -51,7 +51,7 @@ nul_destroy_cred(struct rpc_cred *cred)
* Match cred handle against current process
*/
static int
-nul_match(struct auth_cred *acred, struct rpc_cred *cred, int taskflags)
+nul_match(struct cred *acred, struct rpc_cred *cred, int taskflags)
{
return 1;
}
diff --git a/net/sunrpc/auth_unix.c b/net/sunrpc/auth_unix.c
index 5ed91e5..f5ab6d7 100644
--- a/net/sunrpc/auth_unix.c
+++ b/net/sunrpc/auth_unix.c
@@ -51,13 +51,13 @@ unx_destroy(struct rpc_auth *auth)
* Lookup AUTH_UNIX creds for current process
*/
static struct rpc_cred *
-unx_lookup_cred(struct rpc_auth *auth, struct auth_cred *acred, int flags)
+unx_lookup_cred(struct rpc_auth *auth, struct cred *acred, int flags)
{
return rpcauth_lookup_credcache(auth, acred, flags);
}
static struct rpc_cred *
-unx_create_cred(struct rpc_auth *auth, struct auth_cred *acred, int flags)
+unx_create_cred(struct rpc_auth *auth, struct cred *acred, int flags)
{
struct unx_cred *cred;
int i;
@@ -115,7 +115,7 @@ unx_destroy_cred(struct rpc_cred *cred)
* request root creds (e.g. for NFS swapping).
*/
static int
-unx_match(struct auth_cred *acred, struct rpc_cred *rcred, int flags)
+unx_match(struct cred *acred, struct rpc_cred *rcred, int flags)
{
struct unx_cred *cred = container_of(rcred, struct unx_cred, uc_base);
int i;
diff --git a/security/dummy.c b/security/dummy.c
index 853ec22..62de89c 100644
--- a/security/dummy.c
+++ b/security/dummy.c
@@ -43,10 +43,12 @@ static int dummy_capget (struct task_struct *target, kernel_cap_t * effective,
*permitted |= (~0 & ~CAP_FS_MASK);
*effective |= (~0 & ~CAP_TO_MASK(CAP_SETPCAP) & ~CAP_FS_MASK);
}
- if (target->fsuid == 0) {
+ rcu_read_lock();
+ if (task_cred(target)->uid == 0) {
*permitted |= CAP_FS_MASK;
*effective |= CAP_FS_MASK;
}
+ rcu_read_unlock();
}
return 0;
}
@@ -138,8 +140,11 @@ static void dummy_bprm_apply_creds (struct linux_binprm *bprm, int unsafe)
}
}
- current->suid = current->euid = current->fsuid = bprm->e_uid;
- current->sgid = current->egid = current->fsgid = bprm->e_gid;
+ current->suid = current->euid = bprm->e_uid;
+ current->sgid = current->egid = bprm->e_gid;
+
+ change_fsuid(bprm->cred, bprm->e_uid);
+ change_fsgid(bprm->cred, bprm->e_gid);
dummy_capget(current, ¤t->cap_effective, ¤t->cap_inheritable, ¤t->cap_permitted);
}
@@ -572,7 +577,7 @@ static int dummy_task_prctl (int option, unsigned long arg2, unsigned long arg3,
static void dummy_task_reparent_to_init (struct task_struct *p)
{
- p->euid = p->fsuid = 0;
+ p->euid = 0;
return;
}
diff --git a/security/keys/compat.c b/security/keys/compat.c
index e10ec99..710f87a 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -25,6 +25,12 @@
asmlinkage long compat_sys_keyctl(u32 option,
u32 arg2, u32 arg3, u32 arg4, u32 arg5)
{
+ int ret;
+
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
switch (option) {
case KEYCTL_GET_KEYRING_ID:
return keyctl_get_keyring_ID(arg2, arg3);
diff --git a/security/keys/key.c b/security/keys/key.c
index 01bbc6d..a012e6c 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -817,7 +817,8 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref,
perm |= KEY_USR_WRITE;
/* allocate a new key */
- key = key_alloc(ktype, description, current->fsuid, current->fsgid,
+ key = key_alloc(ktype, description,
+ current->cred->uid, current->cred->gid,
current, perm, flags);
if (IS_ERR(key)) {
key_ref = ERR_PTR(PTR_ERR(key));
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index d9ca15c..2555c3b 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -67,6 +67,10 @@ asmlinkage long sys_add_key(const char __user *_type,
if (plen > 32767)
goto error;
+ ret = update_current_cred();
+ if (ret < 0)
+ goto error;
+
/* draw all the data into kernel space */
ret = key_get_type_from_user(type, _type, sizeof(type));
if (ret < 0)
@@ -143,6 +147,10 @@ asmlinkage long sys_request_key(const char __user *_type,
char type[32], *description, *callout_info;
long ret;
+ ret = update_current_cred();
+ if (ret < 0)
+ goto error;
+
/* pull the type into kernel space */
ret = key_get_type_from_user(type, _type, sizeof(type));
if (ret < 0)
@@ -794,7 +802,7 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
down_write(&key->sem);
/* if we're not the sysadmin, we can only change a key that we own */
- if (capable(CAP_SYS_ADMIN) || key->uid == current->fsuid) {
+ if (capable(CAP_SYS_ADMIN) || key->uid == current->cred->uid) {
key->perm = perm;
ret = 0;
}
@@ -1062,6 +1070,12 @@ error:
asmlinkage long sys_keyctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
+ int ret;
+
+ ret = update_current_cred();
+ if (ret < 0)
+ return ret;
+
switch (option) {
case KEYCTL_GET_KEYRING_ID:
return keyctl_get_keyring_ID((key_serial_t) arg2,
diff --git a/security/keys/permission.c b/security/keys/permission.c
index 3b41f9b..f0f0452 100644
--- a/security/keys/permission.c
+++ b/security/keys/permission.c
@@ -22,14 +22,19 @@ int key_task_permission(const key_ref_t key_ref,
struct task_struct *context,
key_perm_t perm)
{
+ struct cred *cred;
struct key *key;
key_perm_t kperm;
int ret;
+ rcu_read_lock();
+ cred = get_task_cred(context);
+ rcu_read_unlock();
+
key = key_ref_to_ptr(key_ref);
/* use the second 8-bits of permissions for keys the caller owns */
- if (key->uid == context->fsuid) {
+ if (key->uid == cred->uid) {
kperm = key->perm >> 16;
goto use_these_perms;
}
@@ -37,15 +42,12 @@ int key_task_permission(const key_ref_t key_ref,
/* use the third 8-bits of permissions for keys the caller has a group
* membership in common with */
if (key->gid != -1 && key->perm & KEY_GRP_ALL) {
- if (key->gid == context->fsgid) {
+ if (key->gid == cred->gid) {
kperm = key->perm >> 8;
goto use_these_perms;
}
- task_lock(context);
- ret = groups_search(context->group_info, key->gid);
- task_unlock(context);
-
+ ret = groups_search(cred->group_info, key->gid);
if (ret) {
kperm = key->perm >> 8;
goto use_these_perms;
@@ -56,6 +58,8 @@ int key_task_permission(const key_ref_t key_ref,
kperm = key->perm;
use_these_perms:
+ put_cred(cred);
+
/* use the top 8-bits of permissions for keys the caller possesses
* - possessor permissions are additive with other permissions
*/
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index b6f8680..97c99d1 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -357,13 +357,14 @@ int suid_keys(struct task_struct *tsk)
/*****************************************************************************/
/*
* the filesystem user ID changed
+ * - can only be used for current task
*/
void key_fsuid_changed(struct task_struct *tsk)
{
/* update the ownership of the thread keyring */
if (tsk->thread_keyring) {
down_write(&tsk->thread_keyring->sem);
- tsk->thread_keyring->uid = tsk->fsuid;
+ tsk->thread_keyring->uid = tsk->cred->uid;
up_write(&tsk->thread_keyring->sem);
}
@@ -372,13 +373,14 @@ void key_fsuid_changed(struct task_struct *tsk)
/*****************************************************************************/
/*
* the filesystem group ID changed
+ * - can only be used for current task
*/
void key_fsgid_changed(struct task_struct *tsk)
{
/* update the ownership of the thread keyring */
if (tsk->thread_keyring) {
down_write(&tsk->thread_keyring->sem);
- tsk->thread_keyring->gid = tsk->fsgid;
+ tsk->thread_keyring->gid = tsk->cred->gid;
up_write(&tsk->thread_keyring->sem);
}
@@ -398,10 +400,13 @@ key_ref_t search_process_keyrings(struct key_type *type,
struct task_struct *context)
{
struct request_key_auth *rka;
+ struct cred *cred;
key_ref_t key_ref, ret, err;
might_sleep();
+ cred = get_current_cred();
+
/* we want to return -EAGAIN or -ENOKEY if any of the keyrings were
* searchable, but we failed to find a key or we found a negative key;
* otherwise we want to return a sample error (probably -EACCES) if
@@ -414,10 +419,9 @@ key_ref_t search_process_keyrings(struct key_type *type,
err = ERR_PTR(-EAGAIN);
/* search the thread keyring first */
- if (context->thread_keyring) {
- key_ref = keyring_search_aux(
- make_key_ref(context->thread_keyring, 1),
- context, type, description, match);
+ if (cred->thread_keyring) {
+ key_ref = keyring_search_aux(cred->thread_keyring, context,
+ type, description, match);
if (!IS_ERR(key_ref))
goto found;
@@ -435,10 +439,9 @@ key_ref_t search_process_keyrings(struct key_type *type,
}
/* search the process keyring second */
- if (context->signal->process_keyring) {
- key_ref = keyring_search_aux(
- make_key_ref(context->signal->process_keyring, 1),
- context, type, description, match);
+ if (cred->process_keyring) {
+ key_ref = keyring_search_aux(cred->process_keyring, context,
+ type, description, match);
if (!IS_ERR(key_ref))
goto found;
@@ -455,36 +458,10 @@ key_ref_t search_process_keyrings(struct key_type *type,
}
}
- /* search the session keyring */
- if (context->signal->session_keyring) {
- rcu_read_lock();
- key_ref = keyring_search_aux(
- make_key_ref(rcu_dereference(
- context->signal->session_keyring),
- 1),
- context, type, description, match);
- rcu_read_unlock();
-
- if (!IS_ERR(key_ref))
- goto found;
-
- switch (PTR_ERR(key_ref)) {
- case -EAGAIN: /* no key */
- if (ret)
- break;
- case -ENOKEY: /* negative key */
- ret = key_ref;
- break;
- default:
- err = key_ref;
- break;
- }
- }
- /* or search the user-session keyring */
- else {
- key_ref = keyring_search_aux(
- make_key_ref(context->user->session_keyring, 1),
- context, type, description, match);
+ /* search the session or user-session keyring */
+ if (cred->session_keyring) {
+ key_ref = keyring_search_aux(cred->session_keyring, context,
+ type, description, match);
if (!IS_ERR(key_ref))
goto found;
@@ -543,6 +520,7 @@ key_ref_t search_process_keyrings(struct key_type *type,
key_ref = ret ? ret : err;
found:
+ put_cred(cred);
return key_ref;
} /* end search_process_keyrings() */
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 5575001..cf3a9da 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -49,8 +49,8 @@ static int call_sbin_request_key(struct key *key,
/* allocate a new session keyring */
sprintf(desc, "_req.%u", key->serial);
- keyring = keyring_alloc(desc, current->fsuid, current->fsgid, current,
- KEY_ALLOC_QUOTA_OVERRUN, NULL);
+ keyring = keyring_alloc(desc, current->cred->uid, current->cred->gid,
+ current, KEY_ALLOC_QUOTA_OVERRUN, NULL);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error_alloc;
@@ -62,8 +62,8 @@ static int call_sbin_request_key(struct key *key,
goto error_link;
/* record the UID and GID */
- sprintf(uid_str, "%d", current->fsuid);
- sprintf(gid_str, "%d", current->fsgid);
+ sprintf(uid_str, "%d", current->cred->uid);
+ sprintf(gid_str, "%d", current->cred->gid);
/* we say which key is under construction */
sprintf(key_str, "%d", key->serial);
@@ -142,8 +142,8 @@ static struct key *__request_key_construction(struct key_type *type,
/* create a key and add it to the queue */
key = key_alloc(type, description,
- current->fsuid, current->fsgid, current, KEY_POS_ALL,
- flags);
+ current->cred->uid, current->cred->gid, current,
+ KEY_POS_ALL, flags);
if (IS_ERR(key))
goto alloc_failed;
@@ -428,7 +428,7 @@ struct key *request_key_and_link(struct key_type *type,
goto error;
/* - get hold of the user's construction queue */
- user = key_user_lookup(current->fsuid);
+ user = key_user_lookup(current->cred->uid);
if (!user)
goto nomem;
diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
index cbf58a9..8644182 100644
--- a/security/keys/request_key_auth.c
+++ b/security/keys/request_key_auth.c
@@ -185,7 +185,7 @@ struct key *request_key_auth_new(struct key *target, const char *callout_info)
sprintf(desc, "%x", target->serial);
authkey = key_alloc(&key_type_request_key_auth, desc,
- current->fsuid, current->fsgid, current,
+ current->cred->uid, current->cred->gid, current,
KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH |
KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA);
if (IS_ERR(authkey)) {
The attached patch adds a generic intermediary (FS-Cache) by which filesystems
may call on local caching capabilities, and by which local caching backends may
make caches available:
+---------+
| | +--------------+
| NFS |--+ | |
| | | +-->| CacheFS |
+---------+ | +----------+ | | /dev/hda5 |
| | | | +--------------+
+---------+ +-->| | |
| | | |--+
| AFS |----->| FS-Cache |
| | | |--+
+---------+ +-->| | |
| | | | +--------------+
+---------+ | +----------+ | | |
| | | +-->| CacheFiles |
| ISOFS |--+ | /var/cache |
| | +--------------+
+---------+
The patch also documents the netfs interface and the cache backend
interface provided by the facility.
There are a number of reasons why I'm not using i_mapping to do this.
These have been discussed a lot on the LKML and CacheFS mailing lists,
but to summarise the basics:
(1) Most filesystems don't do hole reportage. Holes in files are treated as
blocks of zeros and can't be distinguished otherwise, making it difficult
to distinguish blocks that have been read from the network and cached from
those that haven't.
(2) The backing inode must be fully populated before being exposed to
userspace through the main inode because the VM/VFS goes directly to the
backing inode and does not interrogate the front inode on VM ops.
Therefore:
(a) The backing inode must fit entirely within the cache.
(b) All backed files currently open must fit entirely within the cache at
the same time.
(c) A working set of files in total larger than the cache may not be
cached.
(d) A file may not grow larger than the available space in the cache.
(e) A file that's open and cached, and remotely grows larger than the
cache is potentially stuffed.
(3) Writes go to the backing filesystem, and can only be transferred to the
network when the file is closed.
(4) There's no record of what changes have been made, so the whole file must
be written back.
(5) The pages belong to the backing filesystem, and all metadata associated
with that page are relevant only to the backing filesystem, and not
anything stacked atop it.
The attached patch adds a generic core to which both networking filesystems and
caches may bind. It transfers requests from networking filesystems to
appropriate caches if possible, or else gracefully denies them.
If this facility is disabled in the kernel configuration, then all its
operations will be trivially reducible to nothing by the compiler.
FS-Cache provides the following facilities:
(1) Caches can be added / removed at any time, even whilst in use.
(2) Adds a facility by which tags can be used to refer to caches, even if
they're not mounted yet.
(3) More than one cache can be used at once. Caches can be selected
explicitly by use of tags.
(4) The netfs is provided with an interface that allows either party to
withdraw caching facilities from a file (required for (1)).
(5) A netfs may annotate cache objects that belongs to it.
(6) Cache objects can be pinned and reservations made.
(7) The interface to the netfs returns as few errors as possible, preferring
rather to let the netfs remain oblivious.
(8) Cookies are used to represent indices, files and other objects to the
netfs. The simplest cookie is just a NULL pointer - indicating nothing
cached there.
(9) The netfs is allowed to propose - dynamically - any index hierarchy it
desires, though it must be aware that the index search function is
recursive, stack space is limited, and indices can only be children of
indices.
(10) Indices can be used to group files together to reduce key size and to make
group invalidation easier. The use of indices may make lookup quicker,
but that's cache dependent.
(11) Data I/O is effectively done directly to and from the netfs's pages. The
netfs indicates that page A is at index B of the data-file represented by
cookie C, and that it should be read or written. The cache backend may or
may not start I/O on that page, but if it does, a netfs callback will be
invoked to indicate completion. The I/O may be either synchronous or
asynchronous.
(12) Cookies can be "retired" upon release. At this point FS-Cache will mark
them as obsolete and the index hierarchy rooted at that point will get
recycled.
(13) The netfs provides a "match" function for index searches. In addition to
saying whether a match was made or not, this can also specify that an
entry should be updated or deleted.
FS-Cache maintains a virtual indexing tree in which all indices, files, objects
and pages are kept. Bits of this tree may actually reside in one or more
caches.
FSDEF
|
+------------------------------------+
| |
NFS AFS
| |
+--------------------------+ +-----------+
| | | |
homedir mirror afs.org redhat.com
| | |
+------------+ +---------------+ +----------+
| | | | | |
00001 00002 00007 00125 vol00001 vol00002
| | | | |
+---+---+ +-----+ +---+ +------+------+ +-----+----+
| | | | | | | | | | | | |
PG0 PG1 PG2 PG0 XATTR PG0 PG1 DIRENT DIRENT DIRENT R/W R/O Bak
| |
PG0 +-------+
| |
00001 00003
|
+---+---+
| | |
PG0 PG1 PG2
In the example above, you can see two netfs's being backed: NFS and AFS. These
have different index hierarchies:
(*) The NFS primary index will probably contain per-server indices. Each
server index is indexed by NFS file handles to get data file objects.
Each data file objects can have an array of pages, but may also have
further child objects, such as extended attributes and directory entries.
Extended attribute objects themselves have page-array contents.
(*) The AFS primary index contains per-cell indices. Each cell index contains
per-logical-volume indices. Each of volume index contains up to three
indices for the read-write, read-only and backup mirrors of those volumes.
Each of these contains vnode data file objects, each of which contains an
array of pages.
The very top index is the FS-Cache master index in which individual netfs's
have entries.
Any index object may reside in more than one cache, provided it only has index
children. Any index with non-index object children will be assumed to only
reside in one cache.
The FS-Cache overview can be found in:
Documentation/filesystems/caching/fscache.txt
The netfs API to FS-Cache can be found in:
Documentation/filesystems/caching/netfs-api.txt
The cache backend API to FS-Cache can be found in:
Documentation/filesystems/caching/backend-api.txt
Signed-Off-By: David Howells <[email protected]>
---
Documentation/filesystems/caching/backend-api.txt | 625 +++++++++++++++
Documentation/filesystems/caching/fscache.txt | 295 +++++++
Documentation/filesystems/caching/netfs-api.txt | 741 ++++++++++++++++++
fs/Kconfig | 6
fs/Makefile | 1
fs/fscache/Kconfig | 49 +
fs/fscache/Makefile | 19
fs/fscache/fsc-cache.c | 512 ++++++++++++
fs/fscache/fsc-cookie.c | 494 ++++++++++++
fs/fscache/fsc-fsdef.c | 111 +++
fs/fscache/fsc-internal.h | 376 +++++++++
fs/fscache/fsc-main.c | 127 +++
fs/fscache/fsc-manage.c | 260 ++++++
fs/fscache/fsc-object.c | 586 ++++++++++++++
fs/fscache/fsc-page.c | 866 +++++++++++++++++++++
fs/fscache/fsc-proc.c | 399 ++++++++++
fs/fscache/fsc-stats.c | 103 ++
fs/fscache/fsc-threads.c | 590 ++++++++++++++
include/linux/fscache-cache.h | 433 +++++++++++
include/linux/fscache.h | 593 ++++++++++++++
20 files changed, 7186 insertions(+), 0 deletions(-)
diff --git a/Documentation/filesystems/caching/backend-api.txt b/Documentation/filesystems/caching/backend-api.txt
new file mode 100644
index 0000000..a7e58eb
--- /dev/null
+++ b/Documentation/filesystems/caching/backend-api.txt
@@ -0,0 +1,625 @@
+ ==========================
+ FS-CACHE CACHE BACKEND API
+ ==========================
+
+The FS-Cache system provides an API by which actual caches can be supplied to
+FS-Cache for it to then serve out to network filesystems and other interested
+parties.
+
+This API is declared in <linux/fscache-cache.h>.
+
+
+====================================
+INITIALISING AND REGISTERING A CACHE
+====================================
+
+To start off, a cache definition must be initialised and registered for each
+cache the backend wants to make available. For instance, CacheFS does this in
+the fill_super() operation on mounting.
+
+The cache definition (struct fscache_cache) should be initialised by calling:
+
+ void fscache_init_cache(struct fscache_cache *cache,
+ struct fscache_cache_ops *ops,
+ const char *idfmt,
+ ...);
+
+Where:
+
+ (*) "cache" is a pointer to the cache definition;
+
+ (*) "ops" is a pointer to the table of operations that the backend supports on
+ this cache; and
+
+ (*) "idfmt" is a format and printf-style arguments for constructing a label
+ for the cache.
+
+
+The cache should then be registered with FS-Cache by passing a pointer to the
+previously initialised cache definition to:
+
+ int fscache_add_cache(struct fscache_cache *cache,
+ struct fscache_object *fsdef,
+ const char *tagname);
+
+Two extra arguments should also be supplied:
+
+ (*) "fsdef" which should point to the object representation for the FS-Cache
+ master index in this cache. Netfs primary index entries will be created
+ here.
+
+ (*) "tagname" which, if given, should be a text string naming this cache. If
+ this is NULL, the identifier will be used instead. For CacheFS, the
+ identifier is set to name the underlying block device and the tag can be
+ supplied by mount.
+
+This function may return -ENOMEM if it ran out of memory or -EEXIST if the tag
+is already in use. 0 will be returned on success.
+
+
+=====================
+UNREGISTERING A CACHE
+=====================
+
+A cache can be withdrawn from the system by calling this function with a
+pointer to the cache definition:
+
+ void fscache_withdraw_cache(struct fscache_cache *cache);
+
+In CacheFS's case, this is called by put_super().
+
+
+========
+SECURITY
+========
+
+The cache methods are executed one of two contexts:
+
+ (1) that of the userspace process that issued the netfs operation that caused
+ the cache method to be invoked, or
+
+ (2) that of one of the processes in the FS-Cache thread pool.
+
+In either case, this may not be an appropriate context in which to access the
+cache.
+
+The calling process's fsuid, fsgid and SELinux security identities may need to
+be masqueraded for the duration of the cache driver's access to the cache.
+This is left to the cache to handle; FS-Cache makes no effort in this regard.
+
+
+===================================
+CONTROL AND STATISTICS PRESENTATION
+===================================
+
+The cache may present data to the outside world through FS-Cache's interfaces
+in sysfs and procfs - the former for control and the latter for statistics.
+
+A sysfs directory called /sys/fs/fscache/<cachetag>/ is created if CONFIG_SYSFS
+is enabled. This is accessible through the kobject struct fscache_cache::kobj
+and is for use by the cache as it sees fit.
+
+The cache driver may create itself a directory named for the cache type in the
+/proc/fs/fscache/ directory. This is available if CONFIG_FSCACHE_PROC is
+enabled and is accessible through:
+
+ struct proc_dir_entry *proc_fscache;
+
+
+========================
+RELEVANT DATA STRUCTURES
+========================
+
+ (*) Index/Data file FS-Cache representation cookie:
+
+ struct fscache_cookie {
+ struct fscache_object_def *def;
+ struct fscache_netfs *netfs;
+ void *netfs_data;
+ ...
+ };
+
+ The fields that might be of use to the backend describe the object
+ definition, the netfs definition and the netfs's data for this cookie.
+ The object definition contain functions supplied by the netfs for loading
+ and matching index entries; these are required to provide some of the
+ cache operations.
+
+
+ (*) In-cache object representation:
+
+ struct fscache_object {
+ int debug_id;
+ enum {
+ FSCACHE_OBJECT_RECYCLING,
+ ...
+ } state;
+ spinlock_t lock
+ struct fscache_cache *cache;
+ struct fscache_cookie *cookie;
+ ...
+ };
+
+ Structures of this type should be allocated by the cache backend and
+ passed to FS-Cache when requested by the appropriate cache operation. In
+ the case of CacheFS, they're embedded in CacheFS's internal object
+ structures.
+
+ The debug_id is a simple integer that can be used in debugging messages
+ that refer to a particular object. In such a case it should be printed
+ using "OBJ%x" to be consistent with FS-Cache.
+
+ Each object contains a pointer to the cookie that represents the object it
+ is backing. An object should retired when put_object() is called if it is
+ in state FSCACHE_OBJECT_RECYCLING. The fscache_object struct should be
+ initialised by calling fscache_object_init(object).
+
+
+ (*) FS-Cache operation record:
+
+ struct fscache_operation {
+ atomic_t usage;
+ struct fscache_object *object;
+ unsigned long flags;
+ #define FSCACHE_OP_EXCLUSIVE
+ void (*processor)(struct fscache_operation *op);
+ void (*release)(struct fscache_operation *op);
+ ...
+ };
+
+ FS-Cache has a pool of threads that it uses to give CPU time to the
+ various asynchronous operations that need to be done as part of driving
+ the cache. These are represented by the above structure. The processor
+ method is called to give the op CPU time, and the release method to get
+ rid of it when its usage count reaches 0.
+
+ An operation can be made exclusive upon an object by setting the
+ appropriate flag before enqueuing it with fscache_enqueue_operation(). If
+ an operation needs more processing time, it should be enqueued again.
+
+
+ (*) FS-Cache retrieval operation record:
+
+ struct fscache_retrieval {
+ struct fscache_operation op;
+ struct address_space *mapping;
+ struct list_head *to_do;
+ ...
+ };
+
+ A structure of this type is allocated by FS-Cache to record retrieval and
+ allocation requests made by the netfs. This struct is then passed to the
+ backend to do the operation. The backend may get extra refs to it by
+ calling fscache_get_retrieval() and refs may be discarded by calling
+ fscache_put_retrieval().
+
+ A retrieval operation can be used by the backend to do retrieval work. To
+ do this, the retrieval->op.processor method pointer should be set
+ appropriately by the backend and fscache_enqueue_retrieval() called to
+ submit it to the thread pool. CacheFiles, for example, uses this to queue
+ page examination when it detects PG_lock being cleared.
+
+ The to_do field is an empty list available for the cache backend to use as
+ it sees fit.
+
+
+ (*) FS-Cache storage operation record:
+
+ struct fscache_storage {
+ struct fscache_operation op;
+ pgoff_t store_limit;
+ ...
+ };
+
+ A structure of this type is allocated by FS-Cache to record outstanding
+ writes to be made. FS-Cache itself enqueues this operation and invokes
+ the write_page() method on the object at appropriate times to effect
+ storage.
+
+
+================
+CACHE OPERATIONS
+================
+
+The cache backend provides FS-Cache with a table of operations that can be
+performed on the denizens of the cache. These are held in a structure of type:
+
+ struct fscache_cache_ops
+
+ (*) Name of cache provider [mandatory]:
+
+ const char *name
+
+ This isn't strictly an operation, but should be pointed at a string naming
+ the backend.
+
+
+ (*) Allocate a new object [mandatory]:
+
+ struct fscache_object *(*alloc_object)(struct fscache_cache *cache,
+ struct fscache_cookie *cookie)
+
+ This method is used to allocate a cache object representation to back a
+ cookie in a particular cache. fscache_object_init() should be called on
+ the object to initialise it prior to returning.
+
+ This function may also be used to parse the index key to be used for
+ multiple lookup calls to turn it into a more convenient form. FS-Cache
+ will call the lookup_complete() method to allow the cache to release the
+ form once lookup is complete or aborted.
+
+
+ (*) Look up and create object [mandatory]:
+
+ void (*lookup_object)(struct fscache_object *object)
+
+ This method is used to look up an object, given that the object is already
+ allocated and attached to the cookie. This should instantiate that object
+ in the cache if it can.
+
+ The method should call fscache_object_lookup_negative() as soon as
+ possible if it determines the object doesn't exist in the cache. If the
+ object is found to exist and the netfs indicates that it is valid then
+ fscache_obtained_object() should be called once the object is in a
+ position to have data stored in it. Similarly, fscache_obtained_object()
+ should also be called once a non-present object has been created.
+
+ If a lookup error occurs, fscache_object_lookup_error() should be called
+ to abort the lookup of that object.
+
+
+ (*) Release lookup data [mandatory]:
+
+ void (*lookup_complete)(struct fscache_object *object)
+
+ This method is called to ask the cache to release any resources it was
+ using to perform a lookup.
+
+
+ (*) Increment object refcount [mandatory]:
+
+ struct fscache_object *(*grab_object)(struct fscache_object *object)
+
+ This method is called to increment the reference count on an object. It
+ may fail (for instance if the cache is being withdrawn) by returning NULL.
+ It should return the object pointer if successful.
+
+
+ (*) Lock/Unlock object [mandatory]:
+
+ void (*lock_object)(struct fscache_object *object)
+ void (*unlock_object)(struct fscache_object *object)
+
+ These methods are used to exclusively lock an object. It must be possible
+ to schedule with the lock held, so a spinlock isn't sufficient.
+
+
+ (*) Pin/Unpin object [optional]:
+
+ int (*pin_object)(struct fscache_object *object)
+ void (*unpin_object)(struct fscache_object *object)
+
+ These methods are used to pin an object into the cache. Once pinned an
+ object cannot be reclaimed to make space. Return -ENOSPC if there's not
+ enough space in the cache to permit this.
+
+
+ (*) Update object [mandatory]:
+
+ int (*update_object)(struct fscache_object *object)
+
+ This is called to update the index entry for the specified object. The
+ new information should be in object->cookie->netfs_data. This can be
+ obtained by calling object->cookie->def->get_aux()/get_attr().
+
+
+ (*) Discard object [mandatory]:
+
+ void (*drop_object)(struct fscache_object *object)
+
+ This method is called to indicate that an object has been unbound from its
+ cookie, and that the cache should release the object's resources and
+ retire it if it's in state FSCACHE_OBJECT_RECYCLING.
+
+ This method should not attempt to release any references held by the
+ caller. The caller will invoke the put_object() method as appropriate.
+
+
+ (*) Release object reference [mandatory]:
+
+ void (*put_object)(struct fscache_object *object)
+
+ This method is used to discard a reference to an object. The object may
+ be freed when all the references to it are released.
+
+
+ (*) Synchronise a cache [mandatory]:
+
+ void (*sync)(struct fscache_cache *cache)
+
+ This is called to ask the backend to synchronise a cache with its backing
+ device.
+
+
+ (*) Dissociate a cache [mandatory]:
+
+ void (*dissociate_pages)(struct fscache_cache *cache)
+
+ This is called to ask a cache to perform any page dissociations as part of
+ cache withdrawal.
+
+
+ (*) Notification that the attributes on a netfs file changed [mandatory]:
+
+ int (*attr_changed)(struct fscache_object *object);
+
+ This is called to indicate to the cache that certain attributes on a netfs
+ file have changed (for example the maximum size a file may reach). The
+ cache can read these from the netfs by calling the cookie's get_attr()
+ method.
+
+ The cache may use the file size information to reserve space on the cache.
+ It should also call fscache_set_store_limit() to indicate to FS-Cache the
+ highest byte it's willing to store for an object.
+
+ This method may return -ve if an error occurred or the cache object cannot
+ be expanded. In such a case, the object will be withdrawn from service.
+
+ This operation is run asynchronously from FS-Cache's thread pool, and
+ storage and retrieval operations from the netfs are excluded during the
+ execution of this operation.
+
+
+ (*) Reserve cache space for an object's data [optional]:
+
+ int (*reserve_space)(struct fscache_object *object, loff_t size);
+
+ This is called to request that cache space be reserved to hold the data
+ for an object and the metadata used to track it. Zero size should be
+ taken as request to cancel a reservation.
+
+ This should return 0 if successful, -ENOSPC if there isn't enough space
+ available, or -ENOMEM or -EIO on other errors.
+
+ The reservation may exceed the current size of the object, thus permitting
+ future expansion. If the amount of space consumed by an object would
+ exceed the reservation, it's permitted to refuse requests to allocate
+ pages, but not required. An object may be pruned down to its reservation
+ size if larger than that already.
+
+
+ (*) Request page be read from cache [mandatory]:
+
+ int (*read_or_alloc_page)(struct fscache_retrieval *op,
+ struct page *page,
+ gfp_t gfp)
+
+ This is called to attempt to read a netfs page from the cache, or to
+ reserve a backing block if not. FS-Cache will have done as much checking
+ as it can before calling, but most of the work belongs to the backend.
+
+ If there's no page in the cache, then -ENODATA should be returned if the
+ backend managed to reserve a backing block; -ENOBUFS or -ENOMEM if it
+ didn't.
+
+ If there is suitable data in the cache, then a read operation should be
+ queued and 0 returned. When the read finishes, fscache_end_io() should be
+ called.
+
+ The fscache_mark_pages_cached() should be called for the page if any cache
+ metadata is retained. This will indicate to the netfs that the page needs
+ explicit uncaching. This operation takes a pagevec, thus allowing several
+ pages to be marked at once.
+
+ The retrieval record pointed to by op should be retained for each page
+ queued and released when I/O on the page has been formally ended.
+ fscache_get/put_retrieval() are available for this purpose.
+
+ The retrieval record may be used to get CPU time via the FS-Cache thread
+ pool. If this is desired, the op->op.processor should be set to point to
+ the appropriate processing routine, and fscache_enqueue_retrieval() should
+ be called at an appropriate point to request CPU time. For instance, the
+ retrieval routine could be enqueued upon the completion of a disk read.
+ The to_do field in the retrieval record is provided to aid in this.
+
+ If an I/O error occurs, fscache_io_error() should be called and -ENOBUFS
+ returned if possible or fscache_end_io() called with a suitable error
+ code..
+
+
+ (*) Request pages be read from cache [mandatory]:
+
+ int (*read_or_alloc_pages)(struct fscache_retrieval *op,
+ struct list_head *pages,
+ unsigned *nr_pages,
+ gfp_t gfp)
+
+ This is like the read_or_alloc_page() method, except it is handed a list
+ of pages instead of one page. Any pages on which a read operation is
+ started must be added to the page cache for the specified mapping and also
+ to the LRU. Such pages must also be removed from the pages list and
+ *nr_pages decremented per page.
+
+ If there was an error such as -ENOMEM, then that should be returned; else
+ if one or more pages couldn't be read or allocated, then -ENOBUFS should
+ be returned; else if one or more pages couldn't be read, then -ENODATA
+ should be returned. If all the pages are dispatched then 0 should be
+ returned.
+
+
+ (*) Request page be allocated in the cache [mandatory]:
+
+ int (*allocate_page)(struct fscache_retrieval *op,
+ struct page *page,
+ gfp_t gfp)
+
+ This is like the read_or_alloc_page() method, except that it shouldn't
+ read from the cache, even if there's data there that could be retrieved.
+ It should, however, set up any internal metadata required such that
+ the write_page() method can write to the cache.
+
+ If there's no backing block available, then -ENOBUFS should be returned
+ (or -ENOMEM if there were other problems). If a block is successfully
+ allocated, then the netfs page should be marked and 0 returned.
+
+
+ (*) Request pages be allocated in the cache [mandatory]:
+
+ int (*allocate_pages)(struct fscache_retrieval *op,
+ struct list_head *pages,
+ unsigned *nr_pages,
+ gfp_t gfp)
+
+ This is an multiple page version of the allocate_page() method. pages and
+ nr_pages should be treated as for the read_or_alloc_pages() method.
+
+
+ (*) Request page be written to cache [mandatory]:
+
+ int (*write_page)(struct fscache_storage *op,
+ struct page *page);
+
+ This is called to write from a page on which there was a previously
+ successful read_or_alloc_page() call or similar. FS-Cache filters out
+ pages that don't have mappings.
+
+ This method is called asynchronously from the FS-Cache thread pool. It is
+ not required to actually store anything, provided -ENODATA is then
+ returned to the next read of this page.
+
+ If an error occurred, then a negative error code should be returned,
+ otherwise zero should be returned. FS-Cache will take appropriate action
+ in response to an error, such as withdrawing this object.
+
+ If this method returns success then FS-Cache will inform the netfs
+ appropriately.
+
+
+ (*) Discard retained per-page metadata [mandatory]:
+
+ void (*uncache_page)(struct fscache_object *object, struct page *page)
+
+ This is called when a netfs page is being evicted from the pagecache. The
+ cache backend should tear down any internal representation or tracking it
+ maintains for this page.
+
+
+==================
+FS-CACHE UTILITIES
+==================
+
+FS-Cache provides some utilities that a cache backend may make use of:
+
+ (*) Note occurrence of an I/O error in a cache:
+
+ void fscache_io_error(struct fscache_cache *cache)
+
+ This tells FS-Cache that an I/O error occurred in the cache. After this
+ has been called, only resource dissociation operations (object and page
+ release) will be passed from the netfs to the cache backend for the
+ specified cache.
+
+ This does not actually withdraw the cache. That must be done separately.
+
+
+ (*) Invoke the retrieval I/O completion function:
+
+ void fscache_end_io(struct fscache_retrieval *op, struct page *page,
+ int error);
+
+ This is called to note the end of an attempt to retrieve a page. The
+ error value should be 0 if successful and an error otherwise.
+
+
+ (*) Set highest store limit:
+
+ void fscache_set_store_limit(struct fscache_object *object,
+ loff_t i_size);
+
+ This sets the limit FS-Cache imposes on the highest byte it's willing to
+ try and store for a netfs. Any page over this limit is automatically
+ rejected by fscache_read_alloc_page() and co with -ENOBUFS.
+
+
+ (*) Mark pages as being cached:
+
+ void fscache_mark_pages_cached(struct fscache_retrieval *op,
+ struct pagevec *pagevec);
+
+ This marks a set of pages as being cached. After this has been called,
+ the netfs must call fscache_uncache_page() to unmark the pages.
+
+
+ (*) Initialise a freshly allocated object:
+
+ void fscache_object_init(struct fscache_object *object);
+
+ This initialises all the fields in an object representation.
+
+
+ (*) Indicate negative lookup on an object:
+
+ void fscache_object_lookup_negative(struct fscache_object *object);
+
+ This is called to indicate to FS-Cache that a lookup process for an object
+ found a negative result.
+
+ This changes the state of an object to permit reads pending on lookup
+ completion to go off and start fetching data from the netfs server as it's
+ known at this point that there can't be any data in the cache.
+
+ This may be called multiple times on an object. Only the first call is
+ significant - all subsequent calls are ignored.
+
+
+ (*) Indicate an object has been obtained:
+
+ void fscache_obtained_object(struct fscache_object *object);
+
+ This is called to indicate to FS-Cache that a lookup process for an object
+ produced a positive result, or that an object was created. This should
+ only be called once for any particular object.
+
+ This changes the state of an object to indicate:
+
+ (1) if no call to fscache_object_lookup_negative() has been made on
+ this object, that there may be data available, and that reads can
+ now go and look for it; and
+
+ (2) that writes may now proceed against this object.
+
+
+ (*) Indicate that object lookup failed:
+
+ void fscache_object_lookup_error(struct fscache_object *object);
+
+ This marks an object as having encountered a fatal error (usually EIO)
+ and causes it to move into a state whereby it will be withdrawn as soon
+ as possible.
+
+
+ (*) Get and release references on a retrieval record:
+
+ void fscache_get_retrieval(struct fscache_retrieval *op);
+ void fscache_put_retrieval(struct fscache_retrieval *op);
+
+ These two functions are used to retain a retrieval record whilst doing
+ asynchronous data retrieval and block allocation.
+
+
+ (*) Enqueue a retrieval record for processing.
+
+ void fscache_enqueue_retrieval(struct fscache_retrieval *op);
+
+ This enqueues a retrieval record for processing by the FS-Cache thread
+ pool. One of the threads in the pool will invoke the retrieval record's
+ op->op.processor callback function. This function may be called from
+ within the callback function.
+
+
+ (*) List of object state names:
+
+ const char *fscache_object_states[];
+
+ For debugging purposes, this may be used to turn the state that an object
+ is in into a text string for display purposes.
diff --git a/Documentation/filesystems/caching/fscache.txt b/Documentation/filesystems/caching/fscache.txt
new file mode 100644
index 0000000..b28f2ca
--- /dev/null
+++ b/Documentation/filesystems/caching/fscache.txt
@@ -0,0 +1,295 @@
+ ==========================
+ General Filesystem Caching
+ ==========================
+
+========
+OVERVIEW
+========
+
+This facility is a general purpose cache for network filesystems, though it
+could be used for caching other things such as ISO9660 filesystems too.
+
+FS-Cache mediates between cache backends (such as CacheFS) and network
+filesystems:
+
+ +---------+
+ | | +--------------+
+ | NFS |--+ | |
+ | | | +-->| CacheFS |
+ +---------+ | +----------+ | | /dev/hda5 |
+ | | | | +--------------+
+ +---------+ +-->| | |
+ | | | |--+
+ | AFS |----->| FS-Cache |
+ | | | |--+
+ +---------+ +-->| | |
+ | | | | +--------------+
+ +---------+ | +----------+ | | |
+ | | | +-->| CacheFiles |
+ | ISOFS |--+ | /var/cache |
+ | | +--------------+
+ +---------+
+
+
+FS-Cache does not follow the idea of completely loading every netfs file
+opened in its entirety into a cache before permitting it to be accessed and
+then serving the pages out of that cache rather than the netfs inode because:
+
+ (1) It must be practical to operate without a cache.
+
+ (2) The size of any accessible file must not be limited to the size of the
+ cache.
+
+ (3) The combined size of all opened files (this includes mapped libraries)
+ must not be limited to the size of the cache.
+
+ (4) The user should not be forced to download an entire file just to do a
+ one-off access of a small portion of it (such as might be done with the
+ "file" program).
+
+It instead serves the cache out in PAGE_SIZE chunks as and when requested by
+the netfs('s) using it.
+
+
+FS-Cache provides the following facilities:
+
+ (1) More than one cache can be used at once. Caches can be selected
+ explicitly by use of tags.
+
+ (2) Caches can be added / removed at any time.
+
+ (3) The netfs is provided with an interface that allows either party to
+ withdraw caching facilities from a file (required for (2)).
+
+ (4) The interface to the netfs returns as few errors as possible, preferring
+ rather to let the netfs remain oblivious.
+
+ (5) Cookies are used to represent indices, files and other objects to the
+ netfs. The simplest cookie is just a NULL pointer - indicating nothing
+ cached there.
+
+ (6) The netfs is allowed to propose - dynamically - any index hierarchy it
+ desires, though it must be aware that the index search function is
+ recursive, stack space is limited, and indices can only be children of
+ indices.
+
+ (7) Data I/O is done direct to and from the netfs's pages. The netfs
+ indicates that page A is at index B of the data-file represented by cookie
+ C, and that it should be read or written. The cache backend may or may
+ not start I/O on that page, but if it does, a netfs callback will be
+ invoked to indicate completion. The I/O may be either synchronous or
+ asynchronous.
+
+ (8) Cookies can be "retired" upon release. At this point FS-Cache will mark
+ them as obsolete and the index hierarchy rooted at that point will get
+ recycled.
+
+ (9) The netfs provides a "match" function for index searches. In addition to
+ saying whether a match was made or not, this can also specify that an
+ entry should be updated or deleted.
+
+(10) As much as possible is done asynchronously.
+
+
+FS-Cache maintains a virtual indexing tree in which all indices, files, objects
+and pages are kept. Bits of this tree may actually reside in one or more
+caches.
+
+ FSDEF
+ |
+ +------------------------------------+
+ | |
+ NFS AFS
+ | |
+ +--------------------------+ +-----------+
+ | | | |
+ homedir mirror afs.org redhat.com
+ | | |
+ +------------+ +---------------+ +----------+
+ | | | | | |
+ 00001 00002 00007 00125 vol00001 vol00002
+ | | | | |
+ +---+---+ +-----+ +---+ +------+------+ +-----+----+
+ | | | | | | | | | | | | |
+PG0 PG1 PG2 PG0 XATTR PG0 PG1 DIRENT DIRENT DIRENT R/W R/O Bak
+ | |
+ PG0 +-------+
+ | |
+ 00001 00003
+ |
+ +---+---+
+ | | |
+ PG0 PG1 PG2
+
+In the example above, you can see two netfs's being backed: NFS and AFS. These
+have different index hierarchies:
+
+ (*) The NFS primary index contains per-server indices. Each server index is
+ indexed by NFS file handles to get data file objects. Each data file
+ objects can have an array of pages, but may also have further child
+ objects, such as extended attributes and directory entries. Extended
+ attribute objects themselves have page-array contents.
+
+ (*) The AFS primary index contains per-cell indices. Each cell index contains
+ per-logical-volume indices. Each of volume index contains up to three
+ indices for the read-write, read-only and backup mirrors of those volumes.
+ Each of these contains vnode data file objects, each of which contains an
+ array of pages.
+
+The very top index is the FS-Cache master index in which individual netfs's
+have entries.
+
+Any index object may reside in more than one cache, provided it only has index
+children. Any index with non-index object children will be assumed to only
+reside in one cache.
+
+
+The netfs API to FS-Cache can be found in:
+
+ Documentation/filesystems/caching/netfs-api.txt
+
+The cache backend API to FS-Cache can be found in:
+
+ Documentation/filesystems/caching/backend-api.txt
+
+
+=======================
+STATISTICAL INFORMATION
+=======================
+
+If FS-Cache is compiled with the following options enabled:
+
+ CONFIG_FSCACHE_PROC=y (implied by the following two)
+ CONFIG_FSCACHE_STATS=y
+ CONFIG_FSCACHE_HISTOGRAM=y
+
+then it will gather certain statistics and display them through a number of
+proc files.
+
+ (*) /proc/fs/fscache/stats
+
+ This shows counts of a number of events that can happen in FS-Cache:
+
+ CLASS EVENT MEANING
+ ======= ======= =======================================================
+ Cookies idx=N Number of index cookies allocated
+ dat=N Number of data storage cookies allocated
+ spc=N Number of special cookies allocated
+ Objects alc=N Number of objects allocated
+ nal=N Number of object allocation failures
+ avl=N Number of objects that reached the available state
+ Pages mrk=N Number of pages marked as being cached
+ unc=N Number of uncache page requests seen
+ Acquire n=N Number of acquire cookie requests seen
+ nul=N Number of acq reqs given a NULL parent
+ noc=N Number of acq reqs rejected due to no cache available
+ ok=N Number of acq reqs succeeded
+ nbf=N Number of acq reqs rejected due to error
+ oom=N Number of acq reqs failed on ENOMEM
+ Lookups n=N Number of lookup calls made on cache backends
+ neg=N Number of negative lookups made
+ pos=N Number of positive lookups made
+ crt=N Number of objects created by lookup
+ bst=N Number of objects with boosted lookup priority
+ Updates n=N Number of update cookie requests seen
+ nul=N Number of upd reqs given a NULL parent
+ run=N Number of upd reqs granted CPU time
+ Relinqs n=N Number of relinquish cookie requests seen
+ nul=N Number of rlq reqs given a NULL parent
+ wcr=N Number of rlq reqs waited on completion of creation
+ AttrChg n=N Number of attribute changed requests seen
+ ok=N Number of attr changed requests queued
+ nbf=N Number of attr changed rejected -ENOBUFS
+ oom=N Number of attr changed failed -ENOMEM
+ run=N Number of attr changed ops given CPU time
+ Allocs n=N Number of allocation requests seen
+ ok=N Number of successful alloc reqs
+ wt=N Number of alloc reqs that waited on lookup completion
+ nbf=N Number of alloc reqs rejected -ENOBUFS
+ ops=N Number of alloc reqs submitted
+ owt=N Number of alloc reqs waited for CPU time
+ Retrvls n=N Number of retrieval (read) requests seen
+ ok=N Number of successful retr reqs
+ wt=N Number of retr reqs that waited on lookup completion
+ nod=N Number of retr reqs returned -ENODATA
+ nbf=N Number of retr reqs rejected -ENOBUFS
+ int=N Number of retr reqs aborted -ERESTARTSYS
+ oom=N Number of retr reqs failed -ENOMEM
+ ops=N Number of retr reqs submitted
+ owt=N Number of retr reqs waited for CPU time
+ Stores n=N Number of storage (write) requests seen
+ ok=N Number of successful store reqs
+ agn=N Number of store reqs on a page already pending storage
+ nbf=N Number of store reqs rejected -ENOBUFS
+ oom=N Number of store reqs failed -ENOMEM
+ ops=N Number of store reqs submitted
+ run=N Number of store reqs granted CPU time
+ Ops pend=N Number of times async ops added to pending queues
+ run=N Number of times async ops given CPU time
+ enq=N Number of times async ops queued for processing
+ req=N Number of times async ops requeued for processing
+ rel=N Number of times async ops released
+
+
+ (*) /proc/fs/fscache/pool
+
+ This shows the number of objects and operations each thread in the thread
+ pool has given CPU time to.
+
+
+ (*) /proc/fs/fscache/histogram
+
+ cat /proc/fs/fscache/histogram
+ +HZ +TIME OBJ INST OP RUNS OBJ RUNS RETRV DLY RETRIEVLS
+ ===== ===== ========= ========= ========= ========= =========
+
+ This shows the breakdown of the number of times each amount of time
+ between 0 jiffies and HZ-1 jiffies a variety of tasks took to run. The
+ columns are as follows:
+
+ COLUMN TIME MEASUREMENT
+ ======= =======================================================
+ OBJ INST Length of time to instantiate an object
+ OP RUNS Length of time a call to process an operation took
+ OBJ RUNS Length of time a call to process an object event took
+ RETRV DLY Time between an requesting a read and lookup completing
+ RETRIEVLS Time between beginning and end of a retrieval
+
+ Each row shows the number of events that took a particular range of times.
+ Each step is 1 jiffy in size. The +HZ column indicates the particular
+ jiffy range covered, and the +TIME field the equivalent number of seconds.
+
+
+=========
+DEBUGGING
+=========
+
+The FS-Cache facility can have runtime debugging enabled by adjusting the value
+in:
+
+ /sys/module/fscache/parameters/debug
+
+This is a bitmask of debugging streams to enable:
+
+ BIT VALUE STREAM POINT
+ ======= ======= =============================== =======================
+ 0 1 Cache management Function entry trace
+ 1 2 Function exit trace
+ 2 4 General
+ 3 8 Cookie management Function entry trace
+ 4 16 Function exit trace
+ 5 32 General
+ 6 64 Page handling Function entry trace
+ 7 128 Function exit trace
+ 8 256 General
+ 9 512 Thread pool management Function entry trace
+ 10 1024 Function exit trace
+ 11 2048 General
+
+The appropriate set of values should be OR'd together and the result written to
+the control file. For example:
+
+ echo $((1|8|64)) >/sys/module/fscache/parameters/debug
+
+will turn on all function entry debugging.
+
diff --git a/Documentation/filesystems/caching/netfs-api.txt b/Documentation/filesystems/caching/netfs-api.txt
new file mode 100644
index 0000000..e8a1496
--- /dev/null
+++ b/Documentation/filesystems/caching/netfs-api.txt
@@ -0,0 +1,741 @@
+ ===============================
+ FS-CACHE NETWORK FILESYSTEM API
+ ===============================
+
+There's an API by which a network filesystem can make use of the FS-Cache
+facilities. This is based around a number of principles:
+
+ (1) Caches can store a number of different object types. There are two main
+ object types: indices and files. The first is a special type used by
+ FS-Cache to make finding objects faster and to make retiring of groups of
+ objects easier.
+
+ (2) Every index, file or other object is represented by a cookie. This cookie
+ may or may not have anything associated with it, but the netfs doesn't
+ need to care.
+
+ (3) Barring the top-level index (one entry per cached netfs), the index
+ hierarchy for each netfs is structured according the whim of the netfs.
+
+This API is declared in <linux/fscache.h>.
+
+This document contains the following sections:
+
+ (1) Network filesystem definition
+ (2) Index definition
+ (3) Object definition
+ (4) Network filesystem (un)registration
+ (5) Cache tag lookup
+ (6) Index registration
+ (7) Data file registration
+ (8) Miscellaneous object registration
+ (9) Setting the data file size
+ (10) Page alloc/read/write
+ (11) Page uncaching
+ (12) Index and data file update
+ (13) Miscellaneous cookie operations
+ (14) Cookie unregistration
+ (15) Index and data file invalidation
+
+
+=============================
+NETWORK FILESYSTEM DEFINITION
+=============================
+
+FS-Cache needs a description of the network filesystem. This is specified
+using a record of the following structure:
+
+ struct fscache_netfs {
+ uint32_t version;
+ const char *name;
+ struct fscache_netfs_operations *ops;
+ struct fscache_cookie *primary_index;
+ ...
+ };
+
+This first three fields should be filled in before registration, and the fourth
+will be filled in by the registration function; any other fields should just be
+ignored and are for internal use only.
+
+The fields are:
+
+ (1) The name of the netfs (used as the key in the toplevel index).
+
+ (2) The version of the netfs (if the name matches but the version doesn't, the
+ entire in-cache hierarchy for this netfs will be scrapped and begun
+ afresh).
+
+ (3) The operations table is defined as follows:
+
+ struct fscache_netfs_operations {
+ };
+
+ Currently there aren't any functions here.
+
+ (4) The cookie representing the primary index will be allocated according to
+ another parameter passed into the registration function.
+
+For example, kAFS (linux/fs/afs/) uses the following definitions to describe
+itself:
+
+ static struct fscache_netfs_operations afs_cache_ops = {
+ };
+
+ struct fscache_netfs afs_cache_netfs = {
+ .version = 0,
+ .name = "afs",
+ .ops = &afs_cache_ops,
+ };
+
+
+================
+INDEX DEFINITION
+================
+
+Indices are used for two purposes:
+
+ (1) To aid the finding of a file based on a series of keys (such as AFS's
+ "cell", "volume ID", "vnode ID").
+
+ (2) To make it easier to discard a subset of all the files cached based around
+ a particular key - for instance to mirror the removal of an AFS volume.
+
+However, since it's unlikely that any two netfs's are going to want to define
+their index hierarchies in quite the same way, FS-Cache tries to impose as few
+restraints as possible on how an index is structured and where it is placed in
+the tree. The netfs can even mix indices and data files at the same level, but
+it's not recommended.
+
+Each index entry consists of a key of indeterminate length plus some auxilliary
+data, also of indeterminate length.
+
+There are some limits on indices:
+
+ (1) Any index containing non-index objects should be restricted to a single
+ cache. Any such objects created within an index will be created in the
+ first cache only. The cache in which an index is created can be
+ controlled by cache tags (see below).
+
+ (2) The entry data must be atomically journallable, so it is limited to about
+ 400 bytes at present. At least 400 bytes will be available.
+
+ (3) The depth of the index tree should be judged with care as the search
+ function is recursive. Too many layers will run the kernel out of stack.
+
+
+=================
+OBJECT DEFINITION
+=================
+
+To define an object, a structure of the following type should be filled out:
+
+ struct fscache_cookie_def
+ {
+ uint8_t name[16];
+ uint8_t type;
+
+ struct fscache_cache_tag *(*select_cache)(
+ const void *parent_netfs_data,
+ const void *cookie_netfs_data);
+
+ uint16_t (*get_key)(const void *cookie_netfs_data,
+ void *buffer,
+ uint16_t bufmax);
+
+ void (*get_attr)(const void *cookie_netfs_data,
+ uint64_t *size);
+
+ uint16_t (*get_aux)(const void *cookie_netfs_data,
+ void *buffer,
+ uint16_t bufmax);
+
+ fscache_checkaux_t (*check_aux)(void *cookie_netfs_data,
+ const void *data,
+ uint16_t datalen);
+
+ void (*get_context)(void *cookie_netfs_data, void *context);
+
+ void (*put_context)(void *cookie_netfs_data, void *context);
+
+ void (*mark_pages_cached)(void *cookie_netfs_data,
+ struct address_space *mapping,
+ struct pagevec *cached_pvec);
+
+ void (*now_uncached)(void *cookie_netfs_data);
+ };
+
+This has the following fields:
+
+ (1) The type of the object [mandatory].
+
+ This is one of the following values:
+
+ (*) FSCACHE_COOKIE_TYPE_INDEX
+
+ This defines an index, which is a special FS-Cache type.
+
+ (*) FSCACHE_COOKIE_TYPE_DATAFILE
+
+ This defines an ordinary data file.
+
+ (*) Any other value between 2 and 255
+
+ This defines an extraordinary object such as an XATTR.
+
+ (2) The name of the object type (NUL terminated unless all 16 chars are used)
+ [optional].
+
+ (3) A function to select the cache in which to store an index [optional].
+
+ This function is invoked when an index needs to be instantiated in a cache
+ during the instantiation of a non-index object. Only the immediate index
+ parent for the non-index object will be queried. Any indices above that
+ in the hierarchy may be stored in multiple caches. This function does not
+ need to be supplied for any non-index object or any index that will only
+ have index children.
+
+ If this function is not supplied or if it returns NULL then the first
+ cache in the parent's list will be chosed, or failing that, the first
+ cache in the master list.
+
+ (4) A function to retrieve an object's key from the netfs [mandatory].
+
+ This function will be called with the netfs data that was passed to the
+ cookie acquisition function and the maximum length of key data that it may
+ provide. It should write the required key data into the given buffer and
+ return the quantity it wrote.
+
+ (5) A function to retrieve attribute data from the netfs [optional].
+
+ This function will be called with the netfs data that was passed to the
+ cookie acquisition function. It should return the size of the file if
+ this is a data file. The size may be used to govern how much cache must
+ be reserved for this file in the cache.
+
+ If the function is absent, a file size of 0 is assumed.
+
+ (6) A function to retrieve auxilliary data from the netfs [optional].
+
+ This function will be called with the netfs data that was passed to the
+ cookie acquisition function and the maximum length of auxilliary data that
+ it may provide. It should write the auxilliary data into the given buffer
+ and return the quantity it wrote.
+
+ If this function is absent, the auxilliary data length will be set to 0.
+
+ The length of the auxilliary data buffer may be dependent on the key
+ length. A netfs mustn't rely on being able to provide more than 400 bytes
+ for both.
+
+ (7) A function to check the auxilliary data [optional].
+
+ This function will be called to check that a match found in the cache for
+ this object is valid. For instance with AFS it could check the auxilliary
+ data against the data version number returned by the server to determine
+ whether the index entry in a cache is still valid.
+
+ If this function is absent, it will be assumed that matching objects in a
+ cache are always valid.
+
+ If present, the function should return one of the following values:
+
+ (*) FSCACHE_CHECKAUX_OKAY - the entry is okay as is
+ (*) FSCACHE_CHECKAUX_NEEDS_UPDATE - the entry requires update
+ (*) FSCACHE_CHECKAUX_OBSOLETE - the entry should be deleted
+
+ This function can also be used to extract data from the auxilliary data in
+ the cache and copy it into the netfs's structures.
+
+ (8) A pair of functions to manage contexts for the completion callback
+ [optional].
+
+ The cache read/write functions are passed a context which is then passed
+ to the I/O completion callback function. To ensure this context remains
+ valid until after the I/O completion is called, two functions may be
+ provided: one to get an extra reference on the context, and one to drop a
+ reference to it.
+
+ If the context is not used or is a type of object that won't go out of
+ scope, then these functions are not required. These functions are not
+ required for indices as indices may not contain data. These functions may
+ be called in interrupt context and so may not sleep.
+
+ (9) A function to mark a page as retaining cache metadata [optional].
+
+ This is called by the cache to indicate that it is retaining in-memory
+ information for this page and that the netfs should uncache the page when
+ it has finished. This does not indicate whether there's data on the disk
+ or not. Note that several pages at once may be presented for marking.
+
+ The PG_fscache bit is set on the pages before this function would be
+ called, so the function need not be provided if this is sufficient.
+
+ This function is not required for indices as they're not permitted data.
+
+(10) A function to unmark all the pages retaining cache metadata [mandatory].
+
+ This is called by FS-Cache to indicate that a backing store is being
+ unbound from a cookie and that all the marks on the pages should be
+ cleared to prevent confusion. Note that the cache will have torn down all
+ its tracking information so that the pages don't need to be explicitly
+ uncached.
+
+ This function is not required for indices as they're not permitted data.
+
+
+===================================
+NETWORK FILESYSTEM (UN)REGISTRATION
+===================================
+
+The first step is to declare the network filesystem to the cache. This also
+involves specifying the layout of the primary index (for AFS, this would be the
+"cell" level).
+
+The registration function is:
+
+ int fscache_register_netfs(struct fscache_netfs *netfs);
+
+It just takes a pointer to the netfs definition. It returns 0 or an error as
+appropriate.
+
+For kAFS, registration is done as follows:
+
+ ret = fscache_register_netfs(&afs_cache_netfs);
+
+The last step is, of course, unregistration:
+
+ void fscache_unregister_netfs(struct fscache_netfs *netfs);
+
+
+================
+CACHE TAG LOOKUP
+================
+
+FS-Cache permits the use of more than one cache. To permit particular index
+subtrees to be bound to particular caches, the second step is to look up cache
+representation tags. This step is optional; it can be left entirely up to
+FS-Cache as to which cache should be used. The problem with doing that is that
+FS-Cache will always pick the first cache that was registered.
+
+To get the representation for a named tag:
+
+ struct fscache_cache_tag *fscache_lookup_cache_tag(const char *name);
+
+This takes a text string as the name and returns a representation of a tag. It
+will never return an error. It may return a dummy tag, however, if it runs out
+of memory; this will inhibit caching with this tag.
+
+Any representation so obtained must be released by passing it to this function:
+
+ void fscache_release_cache_tag(struct fscache_cache_tag *tag);
+
+The tag will be retrieved by FS-Cache when it calls the object definition
+operation select_cache().
+
+
+==================
+INDEX REGISTRATION
+==================
+
+The third step is to inform FS-Cache about part of an index hierarchy that can
+be used to locate files. This is done by requesting a cookie for each index in
+the path to the file:
+
+ struct fscache_cookie *
+ fscache_acquire_cookie(struct fscache_cookie *parent,
+ const struct fscache_object_def *def,
+ void *netfs_data);
+
+This function creates an index entry in the index represented by parent,
+filling in the index entry by calling the operations pointed to by def.
+
+Note that this function never returns an error - all errors are handled
+internally. It may, however, return NULL to indicate no cookie. It is quite
+acceptable to pass this token back to this function as the parent to another
+acquisition (or even to the relinquish cookie, read page and write page
+functions - see below).
+
+Note also that no indices are actually created in a cache until a non-index
+object needs to be created somewhere down the hierarchy. Furthermore, an index
+may be created in several different caches independently at different times.
+This is all handled transparently, and the netfs doesn't see any of it.
+
+For example, with AFS, a cell would be added to the primary index. This index
+entry would have a dependent inode containing a volume location index for the
+volume mappings within this cell:
+
+ cell->cache =
+ fscache_acquire_cookie(afs_cache_netfs.primary_index,
+ &afs_cell_cache_index_def,
+ cell);
+
+Then when a volume location was accessed, it would be entered into the cell's
+index and an inode would be allocated that acts as a volume type and hash chain
+combination:
+
+ vlocation->cache =
+ fscache_acquire_cookie(cell->cache,
+ &afs_vlocation_cache_index_def,
+ vlocation);
+
+And then a particular flavour of volume (R/O for example) could be added to
+that index, creating another index for vnodes (AFS inode equivalents):
+
+ volume->cache =
+ fscache_acquire_cookie(vlocation->cache,
+ &afs_volume_cache_index_def,
+ volume);
+
+
+======================
+DATA FILE REGISTRATION
+======================
+
+The fourth step is to request a data file be created in the cache. This is
+identical to index cookie acquisition. The only difference is that the type in
+the object definition should be something other than index type.
+
+ vnode->cache =
+ fscache_acquire_cookie(volume->cache,
+ &afs_vnode_cache_object_def,
+ vnode);
+
+
+=================================
+MISCELLANEOUS OBJECT REGISTRATION
+=================================
+
+An optional step is to request an object of miscellaneous type be created in
+the cache. This is almost identical to index cookie acquisition. The only
+difference is that the type in the object definition should be something other
+than index type. Whilst the parent object could be an index, it's more likely
+it would be some other type of object such as a data file.
+
+ xattr->cache =
+ fscache_acquire_cookie(vnode->cache,
+ &afs_xattr_cache_object_def,
+ xattr);
+
+Miscellaneous objects might be used to store extended attributes or directory
+entries for example.
+
+
+==========================
+SETTING THE DATA FILE SIZE
+==========================
+
+The fifth step is to set the physical attributes of the file, such as its size.
+This doesn't automatically reserve any space in the cache, but permits the
+cache to adjust its metadata for data tracking appropriately:
+
+ int fscache_attr_changed(struct fscache_cookie *cookie);
+
+The cache will return -ENOBUFS if there is no backing cache or if there is no
+space to allocate any extra metadata required in the cache. The attributes
+will be accessed with the get_attr() cookie definition operation.
+
+Note that attempts to read or write data pages in the cache over this size may
+be rebuffed with -ENOBUFS.
+
+This operation schedules an attribute adjustment to happen asynchronously at
+some point in the future, and as such, it may happen after the function returns
+to the caller. The attribute adjustment excludes read and write operations.
+
+
+=====================
+PAGE READ/ALLOC/WRITE
+=====================
+
+And the sixth step is to store and retrieve pages in the cache. There are
+three functions that are used to do this.
+
+Note:
+
+ (1) A page should not be re-read or re-allocated without uncaching it first.
+
+ (2) A read or allocated page must be uncached when the netfs page is released
+ from the pagecache.
+
+ (3) A page should only be written to the cache if previous read or allocated.
+
+This permits the cache to maintain its page tracking in proper order.
+
+
+PAGE READ
+---------
+
+Firstly, the netfs should ask FS-Cache to examine the caches and read the
+contents cached for a particular page of a particular file if present, or else
+allocate space to store the contents if not:
+
+ typedef
+ void (*fscache_rw_complete_t)(struct page *page,
+ void *context,
+ int error);
+
+ int fscache_read_or_alloc_page(struct fscache_cookie *cookie,
+ struct page *page,
+ fscache_rw_complete_t end_io_func,
+ void *context,
+ gfp_t gfp);
+
+The cookie argument must specify a cookie for an object that isn't an index,
+the page specified will have the data loaded into it (and is also used to
+specify the page number), and the gfp argument is used to control how any
+memory allocations made are satisfied.
+
+If the cookie indicates the inode is not cached:
+
+ (1) The function will return -ENOBUFS.
+
+Else if there's a copy of the page resident in the cache:
+
+ (1) The mark_pages_cached() cookie operation will be called on that page.
+
+ (2) The function will submit a request to read the data from the cache's
+ backing device directly into the page specified.
+
+ (3) The function will return 0.
+
+ (4) When the read is complete, end_io_func() will be invoked with:
+
+ (*) The netfs data supplied when the cookie was created.
+
+ (*) The page descriptor.
+
+ (*) The context argument passed to the above function. This will be
+ maintained with the get_context/put_context functions mentioned above.
+
+ (*) An argument that's 0 on success or negative for an error code.
+
+ If an error occurs, it should be assumed that the page contains no usable
+ data.
+
+ end_io_func() will be called in process context if the read is results in
+ an error, but it might be called in interrupt context if the read is
+ successful.
+
+Otherwise, if there's not a copy available in cache, but the cache may be able
+to store the page:
+
+ (1) The mark_pages_cached() cookie operation will be called on that page.
+
+ (2) A block may be reserved in the cache and attached to the object at the
+ appropriate place.
+
+ (3) The function will return -ENODATA.
+
+This function may also return -ENOMEM or -EINTR, in which case it won't have
+read any data from the cache.
+
+
+PAGE ALLOCATE
+-------------
+
+Alternatively, if there's not expected to be any data in the cache for a page
+because the file has been extended, a block can simply be allocated instead:
+
+ int fscache_alloc_page(struct fscache_cookie *cookie,
+ struct page *page,
+ gfp_t gfp);
+
+This is similar to the fscache_read_or_alloc_page() function, except that it
+never reads from the cache. It will return 0 if a block has been allocated,
+rather than -ENODATA as the other would. One or the other must be performed
+before writing to the cache.
+
+The mark_pages_cached() cookie operation will be called on the page if
+successful.
+
+
+PAGE WRITE
+----------
+
+Secondly, if the netfs changes the contents of the page (either due to an
+initial download or if a user performs a write), then the page should be
+written back to the cache:
+
+ int fscache_write_page(struct fscache_cookie *cookie,
+ struct page *page,
+ gfp_t gfp);
+
+The cookie argument must specify a data file cookie, the page specified should
+contain the data to be written (and is also used to specify the page number),
+and the gfp argument is used to control how any memory allocations made are
+satisfied.
+
+The page must have first been read or allocated successfully and must not have
+been uncached before writing is performed.
+
+If the cookie indicates the inode is not cached then:
+
+ (1) The function will return -ENOBUFS.
+
+Else if space can be allocated in the cache to hold this page:
+
+ (1) PG_fscache_write will be set on the page.
+
+ (2) The function will submit a request to write the data to cache's backing
+ device directly from the page specified.
+
+ (3) The function will return 0.
+
+ (4) When the write is complete PG_fscache_write is cleared on the page and
+ anyone waiting for that bit will be woken up.
+
+Else if there's no space available in the cache, -ENOBUFS will be returned. It
+is also possible for the PG_fscache_write bit to be cleared when no write took
+place if unforeseen circumstances arose (such as a disk error).
+
+Writing takes place asynchronously.
+
+
+MULTIPLE PAGE READ
+------------------
+
+A facility is provided to read several pages at once, as requested by the
+readpages() address space operation:
+
+ int fscache_read_or_alloc_pages(struct fscache_cookie *cookie,
+ struct address_space *mapping,
+ struct list_head *pages,
+ int *nr_pages,
+ fscache_rw_complete_t end_io_func,
+ void *context,
+ gfp_t gfp);
+
+This works in a similar way to fscache_read_or_alloc_page(), except:
+
+ (1) Any page it can retrieve data for is removed from pages and nr_pages and
+ dispatched for reading to the disk. Reads of adjacent pages on disk may
+ be merged for greater efficiency.
+
+ (2) The mark_pages_cached() cookie operation will be called on several pages
+ at once if they're being read or allocated.
+
+ (3) If there was an general error, then that error will be returned.
+
+ Else if some pages couldn't be allocated or read, then -ENOBUFS will be
+ returned.
+
+ Else if some pages couldn't be read but were allocated, then -ENODATA will
+ be returned.
+
+ Otherwise, if all pages had reads dispatched, then 0 will be returned, the
+ list will be empty and *nr_pages will be 0.
+
+ (4) end_io_func will be called once for each page being read as the reads
+ complete. It will be called in process context if error != 0, but it may
+ be called in interrupt context if there is no error.
+
+Note that a return of -ENODATA, -ENOBUFS or any other error does not preclude
+some of the pages being read and some being allocated. Those pages will have
+been marked appropriately and will need uncaching.
+
+
+==============
+PAGE UNCACHING
+==============
+
+To uncache a page, this function should be called:
+
+ void fscache_uncache_page(struct fscache_cookie *cookie,
+ struct page *page);
+
+This function permits the cache to release any in-memory representation it
+might be holding for this netfs page. This function must be called once for
+each page on which the read or write page functions above have been called to
+make sure the cache's in-memory tracking information gets torn down.
+
+Note that pages can't be explicitly deleted from the a data file. The whole
+data file must be retired (see the relinquish cookie function below).
+
+Furthermore, note that this does not cancel the asynchronous read or write
+operation started by the read/alloc and write functions.
+
+
+==========================
+INDEX AND DATA FILE UPDATE
+==========================
+
+To request an update of the index data for an index or other object, the
+following function should be called:
+
+ void fscache_update_cookie(struct fscache_cookie *cookie);
+
+This function will refer back to the netfs_data pointer stored in the cookie by
+the acquisition function to obtain the data to write into each revised index
+entry. The update method in the parent index definition will be called to
+transfer the data.
+
+Note that partial updates may happen automatically at other times, such as when
+data blocks are added to a data file object.
+
+
+===============================
+MISCELLANEOUS COOKIE OPERATIONS
+===============================
+
+There are a number of operations that can be used to control cookies:
+
+ (*) Cookie pinning:
+
+ int fscache_pin_cookie(struct fscache_cookie *cookie);
+ void fscache_unpin_cookie(struct fscache_cookie *cookie);
+
+ These operations permit data cookies to be pinned into the cache and to
+ have the pinning removed. They are not permitted on index cookies.
+
+ The pinning function will return 0 if successful, -ENOBUFS in the cookie
+ isn't backed by a cache, -EOPNOTSUPP if the cache doesn't support pinning,
+ -ENOSPC if there isn't enough space to honour the operation, -ENOMEM or
+ -EIO if there's any other problem.
+
+ (*) Data space reservation:
+
+ int fscache_reserve_space(struct fscache_cookie *cookie, loff_t size);
+
+ This permits a netfs to request cache space be reserved to store up to the
+ given amount of a file. It is permitted to ask for more than the current
+ size of the file to allow for future file expansion.
+
+ If size is given as zero then the reservation will be cancelled.
+
+ The function will return 0 if successful, -ENOBUFS in the cookie isn't
+ backed by a cache, -EOPNOTSUPP if the cache doesn't support reservations,
+ -ENOSPC if there isn't enough space to honour the operation, -ENOMEM or
+ -EIO if there's any other problem.
+
+ Note that this doesn't pin an object in a cache; it can still be culled to
+ make space if it's not in use.
+
+
+=====================
+COOKIE UNREGISTRATION
+=====================
+
+To get rid of a cookie, this function should be called.
+
+ void fscache_relinquish_cookie(struct fscache_cookie *cookie,
+ int retire);
+
+If retire is non-zero, then the object will be marked for recycling, and all
+copies of it will be removed from all active caches in which it is present.
+Not only that but all child objects will also be retired.
+
+If retire is zero, then the object may be available again when next the
+acquisition function is called. Retirement here will overrule the pinning on a
+cookie.
+
+One very important note - relinquish must NOT be called for a cookie unless all
+the cookies for "child" indices, objects and pages have been relinquished
+first.
+
+
+================================
+INDEX AND DATA FILE INVALIDATION
+================================
+
+There is no direct way to invalidate an index subtree or a data file. To do
+this, the caller should relinquish and retire the cookie they have, and then
+acquire a new one.
diff --git a/fs/Kconfig b/fs/Kconfig
index f9eed6d..e367141 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -628,6 +628,12 @@ config GENERIC_ACL
bool
select FS_POSIX_ACL
+menu "Caches"
+
+source "fs/fscache/Kconfig"
+
+endmenu
+
if BLOCK
menu "CD-ROM/DVD Filesystems"
diff --git a/fs/Makefile b/fs/Makefile
index 720c29d..4eecc9a 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_PROFILING) += dcookies.o
obj-$(CONFIG_DLM) += dlm/
# Do not add any filesystems before this line
+obj-$(CONFIG_FSCACHE) += fscache/
obj-$(CONFIG_REISERFS_FS) += reiserfs/
obj-$(CONFIG_EXT3_FS) += ext3/ # Before ext2 so root fs can be ext3
obj-$(CONFIG_EXT4DEV_FS) += ext4/ # Before ext2 so root fs can be ext4dev
diff --git a/fs/fscache/Kconfig b/fs/fscache/Kconfig
new file mode 100644
index 0000000..e68c945
--- /dev/null
+++ b/fs/fscache/Kconfig
@@ -0,0 +1,49 @@
+
+config FSCACHE
+ tristate "General filesystem local caching manager"
+ depends on EXPERIMENTAL
+ help
+ This option enables a generic filesystem caching manager that can be
+ used by various network and other filesystems to cache data locally.
+ Different sorts of caches can be plugged in, depending on the
+ resources available.
+
+ See Documentation/filesystems/caching/fscache.txt for more information.
+
+config FSCACHE_PROC
+ bool "Provide /proc interface for local caching statistics"
+ depends on FSCACHE && PROC_FS
+
+config FSCACHE_STATS
+ bool "Gather statistical information on local caching"
+ depends on FSCACHE_PROC
+ help
+ This option causes statistical information to be gathered on local
+ caching and exported through files:
+
+ /proc/fs/fscache/stats
+ /proc/fs/fscache/pool
+
+ See Documentation/filesystems/caching/fscache.txt for more information.
+
+config FSCACHE_HISTOGRAM
+ bool "Gather latency information on local caching"
+ depends on FSCACHE_PROC
+ help
+
+ This option causes latency information to be gathered on local
+ caching and exported through file:
+
+ /proc/fs/fscache/histogram
+
+ See Documentation/filesystems/caching/fscache.txt for more information.
+
+config FSCACHE_DEBUG
+ bool "Debug FS-Cache"
+ depends on FSCACHE
+ help
+ This permits debugging to be dynamically enabled in the local caching
+ management module. If this is set, the debugging output may be
+ enabled by setting bits in /sys/modules/fscache/parameter/debug.
+
+ See Documentation/filesystems/caching/fscache.txt for more information.
diff --git a/fs/fscache/Makefile b/fs/fscache/Makefile
new file mode 100644
index 0000000..e60dad3
--- /dev/null
+++ b/fs/fscache/Makefile
@@ -0,0 +1,19 @@
+#
+# Makefile for general filesystem caching code
+#
+
+fscache-y := \
+ fsc-cache.o \
+ fsc-cookie.o \
+ fsc-fsdef.o \
+ fsc-main.o \
+ fsc-manage.o \
+ fsc-object.o \
+ fsc-page.o \
+ fsc-threads.o
+
+fscache-$(CONFIG_FSCACHE_PROC) += \
+ fsc-proc.o \
+ fsc-stats.o
+
+obj-$(CONFIG_FSCACHE) := fscache.o
diff --git a/fs/fscache/fsc-cache.c b/fs/fscache/fsc-cache.c
new file mode 100644
index 0000000..1d40058
--- /dev/null
+++ b/fs/fscache/fsc-cache.c
@@ -0,0 +1,512 @@
+/* FS-Cache cache handling
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL CACHE
+#include <linux/module.h>
+#include <linux/slab.h>
+#include "fsc-internal.h"
+
+LIST_HEAD(fscache_cache_list);
+DECLARE_RWSEM(fscache_addremove_sem);
+DECLARE_WAIT_QUEUE_HEAD(fscache_clearance_wq);
+static LIST_HEAD(fscache_cache_tag_list);
+static LIST_HEAD(fscache_netfs_list);
+
+static struct sysfs_ops fscache_cache_sysfs_ops = {
+};
+
+static struct kobj_type fscache_cache_ktype = {
+ .sysfs_ops = &fscache_cache_sysfs_ops,
+};
+
+/*
+ * look up a cache tag
+ */
+struct fscache_cache_tag *__fscache_lookup_cache_tag(const char *name)
+{
+ struct fscache_cache_tag *tag, *xtag;
+
+ /* firstly check for the existence of the tag under read lock */
+ down_read(&fscache_addremove_sem);
+
+ list_for_each_entry(tag, &fscache_cache_tag_list, link) {
+ if (strcmp(tag->name, name) == 0) {
+ atomic_inc(&tag->usage);
+ up_read(&fscache_addremove_sem);
+ return tag;
+ }
+ }
+
+ up_read(&fscache_addremove_sem);
+
+ /* the tag does not exist - create a candidate */
+ xtag = kzalloc(sizeof(*xtag) + strlen(name) + 1, GFP_KERNEL);
+ if (!xtag)
+ /* return a dummy tag if out of memory */
+ return ERR_PTR(-ENOMEM);
+
+ atomic_set(&xtag->usage, 1);
+ strcpy(xtag->name, name);
+
+ /* write lock, search again and add if still not present */
+ down_write(&fscache_addremove_sem);
+
+ list_for_each_entry(tag, &fscache_cache_tag_list, link) {
+ if (strcmp(tag->name, name) == 0) {
+ atomic_inc(&tag->usage);
+ up_write(&fscache_addremove_sem);
+ kfree(xtag);
+ return tag;
+ }
+ }
+
+ list_add_tail(&xtag->link, &fscache_cache_tag_list);
+ up_write(&fscache_addremove_sem);
+ return xtag;
+}
+
+/*
+ * release a reference to a cache tag
+ */
+void __fscache_release_cache_tag(struct fscache_cache_tag *tag)
+{
+ if (tag != ERR_PTR(-ENOMEM)) {
+ down_write(&fscache_addremove_sem);
+
+ if (atomic_dec_and_test(&tag->usage))
+ list_del_init(&tag->link);
+ else
+ tag = NULL;
+
+ up_write(&fscache_addremove_sem);
+
+ kfree(tag);
+ }
+}
+
+/*
+ * register a network filesystem for caching
+ */
+int __fscache_register_netfs(struct fscache_netfs *netfs)
+{
+ struct fscache_netfs *ptr;
+ int ret;
+
+ _enter("{%s}", netfs->name);
+
+ INIT_LIST_HEAD(&netfs->link);
+
+ /* allocate a cookie for the primary index */
+ netfs->primary_index =
+ kmem_cache_zalloc(fscache_cookie_jar, GFP_KERNEL);
+
+ if (!netfs->primary_index) {
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+ }
+
+ /* initialise the primary index cookie */
+ atomic_set(&netfs->primary_index->usage, 1);
+ atomic_set(&netfs->primary_index->n_children, 0);
+
+ netfs->primary_index->def = &fscache_fsdef_netfs_def;
+ netfs->primary_index->parent = &fscache_fsdef_index;
+ netfs->primary_index->netfs_data = netfs;
+
+ atomic_inc(&netfs->primary_index->parent->usage);
+ atomic_inc(&netfs->primary_index->parent->n_children);
+
+ spin_lock_init(&netfs->primary_index->lock);
+ INIT_HLIST_HEAD(&netfs->primary_index->backing_objects);
+
+ /* check the netfs type is not already present */
+ down_write(&fscache_addremove_sem);
+
+ ret = -EEXIST;
+ list_for_each_entry(ptr, &fscache_netfs_list, link) {
+ if (strcmp(ptr->name, netfs->name) == 0)
+ goto already_registered;
+ }
+
+ list_add(&netfs->link, &fscache_netfs_list);
+ ret = 0;
+
+ printk(KERN_NOTICE "FS-Cache: Netfs '%s' registered for caching\n",
+ netfs->name);
+
+already_registered:
+ up_write(&fscache_addremove_sem);
+
+ if (ret < 0) {
+ netfs->primary_index->parent = NULL;
+ __fscache_cookie_put(netfs->primary_index);
+ netfs->primary_index = NULL;
+ }
+
+ _leave(" = %d", ret);
+ return ret;
+}
+
+EXPORT_SYMBOL(__fscache_register_netfs);
+
+/*
+ * unregister a network filesystem from the cache
+ * - all cookies must have been released first
+ */
+void __fscache_unregister_netfs(struct fscache_netfs *netfs)
+{
+ _enter("{%s.%u}", netfs->name, netfs->version);
+
+ down_write(&fscache_addremove_sem);
+
+ list_del(&netfs->link);
+ fscache_relinquish_cookie(netfs->primary_index, 0);
+
+ up_write(&fscache_addremove_sem);
+
+ printk(KERN_NOTICE "FS-Cache: Netfs '%s' unregistered from caching\n",
+ netfs->name);
+
+ _leave("");
+}
+
+EXPORT_SYMBOL(__fscache_unregister_netfs);
+
+/**
+ * fscache_init_cache - Initialise a cache record
+ * @cache: The cache record to be initialised
+ * @ops: The cache operations to be installed in that record
+ * @idfmt: Format string to define identifier
+ * @...: sprintf-style arguments
+ *
+ * Initialise a record of a cache and fill in the name.
+ *
+ * See Documentation/filesystems/caching/backend-api.txt for a complete
+ * description.
+ */
+void fscache_init_cache(struct fscache_cache *cache,
+ const struct fscache_cache_ops *ops,
+ const char *idfmt,
+ ...)
+{
+ va_list va;
+
+ memset(cache, 0, sizeof(*cache));
+
+ cache->ops = ops;
+ cache->kobj.kset = &fscache_kset;
+ cache->kobj.ktype = &fscache_cache_ktype;
+
+ va_start(va, idfmt);
+ vsnprintf(cache->identifier, sizeof(cache->identifier), idfmt, va);
+ va_end(va);
+
+ INIT_LIST_HEAD(&cache->link);
+ INIT_LIST_HEAD(&cache->object_list);
+ spin_lock_init(&cache->object_list_lock);
+}
+
+EXPORT_SYMBOL(fscache_init_cache);
+
+/**
+ * fscache_add_cache - Declare a cache as being open for business
+ * @cache: The record describing the cache
+ * @ifsdef: The record of the cache object describing the top-level index
+ * @tagname: The tag describing this cache
+ *
+ * Add a cache to the system, making it available for netfs's to use.
+ *
+ * See Documentation/filesystems/caching/backend-api.txt for a complete
+ * description.
+ */
+int fscache_add_cache(struct fscache_cache *cache,
+ struct fscache_object *ifsdef,
+ const char *tagname)
+{
+ struct fscache_cache_tag *tag;
+ int ret;
+
+ BUG_ON(!cache->ops);
+ BUG_ON(!ifsdef);
+
+ /* make sure the worker threads are present */
+ down_write(&fscache_addremove_sem);
+ fscache_init_threads();
+ up_write(&fscache_addremove_sem);
+
+ cache->flags = 0;
+ ifsdef->event_mask = ULONG_MAX & ~(1 << FSCACHE_OBJECT_EV_CLEARED);
+ ifsdef->state = FSCACHE_OBJECT_ACTIVE;
+
+ if (!tagname)
+ tagname = cache->identifier;
+
+ BUG_ON(!tagname[0]);
+
+ _enter("{%s.%s},,%s", cache->ops->name, cache->identifier, tagname);
+
+ /* we use the cache tag to uniquely identify caches */
+ tag = __fscache_lookup_cache_tag(tagname);
+ if (IS_ERR(tag))
+ goto nomem;
+
+ if (test_and_set_bit(FSCACHE_TAG_RESERVED, &tag->flags))
+ goto tag_in_use;
+
+ ret = kobject_set_name(&cache->kobj, "%s", tagname);
+ if (ret < 0)
+ goto error;
+
+ ret = kobject_register(&cache->kobj);
+ if (ret < 0)
+ goto error;
+
+ if (!cache->ops->grab_object(ifsdef))
+ BUG();
+
+ ifsdef->cookie = &fscache_fsdef_index;
+ ifsdef->cache = cache;
+ cache->fsdef = ifsdef;
+
+ down_write(&fscache_addremove_sem);
+
+ tag->cache = cache;
+ cache->tag = tag;
+
+ /* add the cache to the list */
+ list_add(&cache->link, &fscache_cache_list);
+
+ /* add the cache's netfs definition index object to the cache's
+ * list */
+ spin_lock(&cache->object_list_lock);
+ list_add_tail(&ifsdef->cache_link, &cache->object_list);
+ spin_unlock(&cache->object_list_lock);
+
+ /* add the cache's netfs definition index object to the top level index
+ * cookie as a known backing object */
+ spin_lock(&fscache_fsdef_index.lock);
+
+ hlist_add_head(&ifsdef->cookie_link,
+ &fscache_fsdef_index.backing_objects);
+
+ atomic_inc(&fscache_fsdef_index.usage);
+
+ /* done */
+ spin_unlock(&fscache_fsdef_index.lock);
+ up_write(&fscache_addremove_sem);
+
+ printk(KERN_NOTICE "FS-Cache: Cache \"%s\" added (type %s)\n",
+ cache->tag->name, cache->ops->name);
+
+ _leave(" = 0 [%s]", cache->identifier);
+ return 0;
+
+tag_in_use:
+ printk(KERN_ERR "FS-Cache: Cache tag '%s' already in use\n", tagname);
+ __fscache_release_cache_tag(tag);
+ _leave(" = -EXIST");
+ return -EEXIST;
+
+error:
+ __fscache_release_cache_tag(tag);
+ _leave(" = %d", ret);
+ return ret;
+
+nomem:
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+}
+
+EXPORT_SYMBOL(fscache_add_cache);
+
+/*
+ * select a cache in which to store an object
+ * - the cache addremove semaphore must be at least read-locked by the caller
+ * - the object will never be an index
+ */
+struct fscache_cache *fscache_select_cache_for_object(
+ struct fscache_cookie *cookie)
+{
+ struct fscache_cache_tag *tag;
+ struct fscache_object *object;
+ struct fscache_cache *cache;
+
+ _enter("");
+
+ if (list_empty(&fscache_cache_list)) {
+ _leave(" = NULL [no cache]");
+ return NULL;
+ }
+
+ /* we check the parent to determine the cache to use */
+ spin_lock(&cookie->lock);
+
+ /* the first in the parent's backing list should be the preferred
+ * cache */
+ if (!hlist_empty(&cookie->backing_objects)) {
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+
+ cache = object->cache;
+ if (object->state >= FSCACHE_OBJECT_DYING ||
+ test_bit(FSCACHE_IOERROR, &cache->flags))
+ cache = NULL;
+
+ spin_unlock(&cookie->lock);
+ _leave(" = %p [parent]", cache);
+ return cache;
+ }
+
+ /* the parent is unbacked */
+ if (cookie->def->type != FSCACHE_COOKIE_TYPE_INDEX) {
+ /* cookie not an index and is unbacked */
+ spin_unlock(&cookie->lock);
+ _leave(" = NULL [cookie ub,ni]");
+ return NULL;
+ }
+
+ spin_unlock(&cookie->lock);
+
+ if (!cookie->def->select_cache)
+ goto no_preference;
+
+ /* ask the netfs for its preference */
+ tag = cookie->def->select_cache(cookie->parent->netfs_data,
+ cookie->netfs_data);
+ if (!tag)
+ goto no_preference;
+
+ if (tag == ERR_PTR(-ENOMEM)) {
+ _leave(" = NULL [nomem tag]");
+ return NULL;
+ }
+
+ if (!tag->cache) {
+ _leave(" = NULL [unbacked tag]");
+ return NULL;
+ }
+
+ if (test_bit(FSCACHE_IOERROR, &tag->cache->flags))
+ return NULL;
+
+ _leave(" = %p [specific]", tag->cache);
+ return tag->cache;
+
+no_preference:
+ /* netfs has no preference - just select first cache */
+ cache = list_entry(fscache_cache_list.next,
+ struct fscache_cache, link);
+ _leave(" = %p [first]", cache);
+ return cache;
+}
+
+/**
+ * fscache_io_error - Note a cache I/O error
+ * @cache: The record describing the cache
+ *
+ * Note that an I/O error occurred in a cache and that it should no longer be
+ * used for anything. This also reports the error into the kernel log.
+ *
+ * See Documentation/filesystems/caching/backend-api.txt for a complete
+ * description.
+ */
+void fscache_io_error(struct fscache_cache *cache)
+{
+ set_bit(FSCACHE_IOERROR, &cache->flags);
+
+ printk(KERN_ERR "FS-Cache: Cache %s stopped due to I/O error\n",
+ cache->ops->name);
+}
+
+EXPORT_SYMBOL(fscache_io_error);
+
+/**
+ * fscache_withdraw_cache - Withdraw a cache from the active service
+ * @cache: The record describing the cache
+ *
+ * Withdraw a cache from service, unbinding all its cache objects from the
+ * netfs cookies they're currently representing.
+ *
+ * See Documentation/filesystems/caching/backend-api.txt for a complete
+ * description.
+ */
+void fscache_withdraw_cache(struct fscache_cache *cache)
+{
+ struct fscache_object *object;
+ LIST_HEAD(object_list);
+
+ _enter("");
+
+ printk(KERN_NOTICE "FS-Cache: Withdrawing cache \"%s\"\n",
+ cache->tag->name);
+
+ /* make the cache unavailable for cookie acquisition */
+ if (test_and_set_bit(FSCACHE_CACHE_WITHDRAWN, &cache->flags))
+ BUG();
+
+ down_write(&fscache_addremove_sem);
+ list_del_init(&cache->link);
+ cache->tag->cache = NULL;
+ up_write(&fscache_addremove_sem);
+
+ /* make sure all pages pinned by operations on behalf of the netfs are
+ * written to disk */
+ cache->ops->sync_cache(cache);
+
+ /* dissociate all the netfs pages backed by this cache from the block
+ * mappings in the cache */
+ cache->ops->dissociate_pages(cache);
+
+ /* we now have to destroy all the active objects pertaining to this
+ * cache - which we do by passing them off to thread pool to dispose
+ * of */
+ _debug("destroy");
+
+ spin_lock(&cache->object_list_lock);
+
+ while (!list_empty(&cache->object_list)) {
+ object = list_entry(cache->object_list.next,
+ struct fscache_object, cache_link);
+ list_move_tail(&object->cache_link, &object_list);
+
+ _debug("withdraw %p", object->cookie);
+
+ spin_lock(&object->lock);
+ spin_unlock(&cache->object_list_lock);
+ fscache_raise_event(object, FSCACHE_OBJECT_EV_WITHDRAW);
+ spin_unlock(&object->lock);
+
+ cond_resched();
+ spin_lock(&cache->object_list_lock);
+ }
+
+ spin_unlock(&cache->object_list_lock);
+
+ /* wait for all extant objects to finish their outstanding operations
+ * and go away */
+ _debug("wait for finish");
+ wait_event(fscache_clearance_wq,
+ atomic_read(&cache->thread_usage) == 0);
+ _debug("wait for clearance");
+ wait_event(fscache_clearance_wq,
+ list_empty(&cache->object_list));
+ _debug("cleared");
+
+ kobject_unregister(&cache->kobj);
+
+ clear_bit(FSCACHE_TAG_RESERVED, &cache->tag->flags);
+ fscache_release_cache_tag(cache->tag);
+ cache->tag = NULL;
+
+ _leave("");
+}
+
+EXPORT_SYMBOL(fscache_withdraw_cache);
diff --git a/fs/fscache/fsc-cookie.c b/fs/fscache/fsc-cookie.c
new file mode 100644
index 0000000..92bfc73
--- /dev/null
+++ b/fs/fscache/fsc-cookie.c
@@ -0,0 +1,494 @@
+/* netfs cookie management
+ *
+ * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL COOKIE
+#include <linux/module.h>
+#include <linux/slab.h>
+#include "fsc-internal.h"
+
+struct kmem_cache *fscache_cookie_jar;
+
+static atomic_t fscache_object_debug_id = ATOMIC_INIT(0);
+
+static int fscache_acquire_non_index_cookie(struct fscache_cookie *cookie);
+static int fscache_alloc_object(struct fscache_cache *cache,
+ struct fscache_cookie *cookie);
+static int fscache_attach_object(struct fscache_cookie *cookie,
+ struct fscache_object *object);
+
+/*
+ * initialise an cookie jar slab element prior to any use
+ */
+void fscache_cookie_init_once(void *_cookie, struct kmem_cache *cachep,
+ unsigned long flags)
+{
+ struct fscache_cookie *cookie = _cookie;
+
+ memset(cookie, 0, sizeof(*cookie));
+ spin_lock_init(&cookie->lock);
+ INIT_HLIST_HEAD(&cookie->backing_objects);
+}
+
+/*
+ * request a cookie to represent an object (index, datafile, xattr, etc)
+ * - parent specifies the parent object
+ * - the top level index cookie for each netfs is stored in the fscache_netfs
+ * struct upon registration
+ * - def points to the definition
+ * - the netfs_data will be passed to the functions pointed to in *def
+ * - all attached caches will be searched to see if they contain this object
+ * - index objects aren't stored on disk until there's a dependent file that
+ * needs storing
+ * - other objects are stored in a selected cache immediately, and all the
+ * indices forming the path to it are instantiated if necessary
+ * - we never let on to the netfs about errors
+ * - we may set a negative cookie pointer, but that's okay
+ */
+struct fscache_cookie *__fscache_acquire_cookie(struct fscache_cookie *parent,
+ const struct fscache_cookie_def *def,
+ void *netfs_data)
+{
+ struct fscache_cookie *cookie;
+
+ BUG_ON(!def);
+
+ _enter("{%s},{%s},%p",
+ parent ? (char *) parent->def->name : "<no-parent>",
+ def->name, netfs_data);
+
+ fscache_stat(&fscache_n_acquires);
+
+ /* if there's no parent cookie, then we don't create one here either */
+ if (!parent) {
+ fscache_stat(&fscache_n_acquires_null);
+ _leave(" [no parent]");
+ return NULL;
+ }
+
+ /* validate the definition */
+ BUG_ON(!def->get_key);
+ BUG_ON(!def->name[0]);
+
+ BUG_ON(def->type == FSCACHE_COOKIE_TYPE_INDEX &&
+ parent->def->type != FSCACHE_COOKIE_TYPE_INDEX);
+
+ /* allocate and initialise a cookie */
+ cookie = kmem_cache_alloc(fscache_cookie_jar, GFP_KERNEL);
+ if (!cookie) {
+ fscache_stat(&fscache_n_acquires_oom);
+ _leave(" [ENOMEM]");
+ return NULL;
+ }
+
+ atomic_set(&cookie->usage, 1);
+ atomic_set(&cookie->n_children, 0);
+
+ atomic_inc(&parent->usage);
+ atomic_inc(&parent->n_children);
+
+ cookie->def = def;
+ cookie->parent = parent;
+ cookie->netfs_data = netfs_data;
+ cookie->flags = 0;
+
+ switch (cookie->def->type) {
+ case FSCACHE_COOKIE_TYPE_INDEX:
+ fscache_stat(&fscache_n_cookie_index);
+ break;
+ case FSCACHE_COOKIE_TYPE_DATAFILE:
+ fscache_stat(&fscache_n_cookie_data);
+ break;
+ default:
+ fscache_stat(&fscache_n_cookie_special);
+ break;
+ }
+
+ /* if the object is an index then we need do nothing more here - we
+ * create indices on disk when we need them as an index may exist in
+ * multiple caches */
+ if (cookie->def->type != FSCACHE_COOKIE_TYPE_INDEX) {
+ if (fscache_acquire_non_index_cookie(cookie) < 0) {
+ atomic_dec(&parent->n_children);
+ __fscache_cookie_put(cookie);
+ fscache_stat(&fscache_n_acquires_nobufs);
+ _leave(" = NULL");
+ return NULL;
+ }
+ }
+
+ fscache_stat(&fscache_n_acquires_ok);
+ _leave(" = %p", cookie);
+ return cookie;
+}
+
+EXPORT_SYMBOL(__fscache_acquire_cookie);
+
+/*
+ * acquire a non-index cookie
+ * - this must make sure the index chain is instantiated and instantiate the
+ * object representation too
+ */
+static int fscache_acquire_non_index_cookie(struct fscache_cookie *cookie)
+{
+ struct fscache_object *object;
+ struct fscache_cache *cache;
+ loff_t i_size;
+ int ret;
+
+ _enter("");
+
+ cookie->flags = 1 << FSCACHE_COOKIE_UNAVAILABLE;
+
+ /* now we need to see whether the backing objects for this cookie yet
+ * exist, if not there'll be nothing to search */
+ down_read(&fscache_addremove_sem);
+
+ if (list_empty(&fscache_cache_list)) {
+ up_read(&fscache_addremove_sem);
+ _leave(" = 0 [no caches]");
+ return 0;
+ }
+
+ /* select a cache in which to store the object */
+ cache = fscache_select_cache_for_object(cookie->parent);
+ if (!cache) {
+ up_read(&fscache_addremove_sem);
+ fscache_stat(&fscache_n_acquires_no_cache);
+ _leave(" = -ENOMEDIUM [no cache]");
+ return -ENOMEDIUM;
+ }
+
+ _debug("cache %s", cache->tag->name);
+
+ cookie->flags =
+ (1 << FSCACHE_COOKIE_LOOKING_UP) |
+ (1 << FSCACHE_COOKIE_CREATING) |
+ (1 << FSCACHE_COOKIE_NO_DATA_YET);
+
+ /* ask the cache to allocate objects for this cookie and its parent
+ * chain */
+ ret = fscache_alloc_object(cache, cookie);
+ if (ret < 0) {
+ up_read(&fscache_addremove_sem);
+ _leave(" = %d", ret);
+ return ret;
+ }
+
+ /* initiate the process of looking up all the objects in the chain */
+ cookie->def->get_attr(cookie->netfs_data, &i_size);
+
+ spin_lock(&cookie->lock);
+ if (hlist_empty(&cookie->backing_objects)) {
+ spin_unlock(&cookie->lock);
+ goto unavailable;
+ }
+
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+
+ fscache_set_store_limit(object, i_size);
+ fscache_enqueue_object(object);
+ spin_unlock(&cookie->lock);
+
+ /* we may be required to wait for lookup to complete at this point */
+ if (!fscache_defer_lookup) {
+ _debug("non-deferred lookup %p", &cookie->flags);
+ wait_on_bit(&cookie->flags, FSCACHE_COOKIE_LOOKING_UP,
+ fscache_wait_bit, TASK_UNINTERRUPTIBLE);
+ _debug("complete");
+ if (test_bit(FSCACHE_COOKIE_UNAVAILABLE, &cookie->flags))
+ goto unavailable;
+ }
+
+ up_read(&fscache_addremove_sem);
+ _leave(" = 0 [deferred]");
+ return 0;
+
+unavailable:
+ up_read(&fscache_addremove_sem);
+ _leave(" = -ENOBUFS");
+ return -ENOBUFS;
+}
+
+/*
+ * recursively allocate cache object records for a cookie/cache combination
+ * - caller must be holding the addremove sem
+ */
+static int fscache_alloc_object(struct fscache_cache *cache,
+ struct fscache_cookie *cookie)
+{
+ struct fscache_object *object;
+ struct hlist_node *_n;
+ int ret;
+
+ _enter("%p,%p{%s}", cache, cookie, cookie->def->name);
+
+ spin_lock(&cookie->lock);
+ hlist_for_each_entry(object, _n, &cookie->backing_objects,
+ cookie_link) {
+ if (object->cache == cache)
+ goto object_already_extant;
+ }
+ spin_unlock(&cookie->lock);
+
+ /* ask the cache to allocate an object (we may end up with duplicate
+ * objects at this stage, but we sort that out later) */
+ object = cache->ops->alloc_object(cache, cookie);
+ if (IS_ERR(object)) {
+ fscache_stat(&fscache_n_object_no_alloc);
+ ret = PTR_ERR(object);
+ goto error;
+ }
+
+ fscache_stat(&fscache_n_object_alloc);
+
+ object->debug_id = atomic_inc_return(&fscache_object_debug_id);
+
+ _debug("ALLOC OBJ%x: %s {%lx}",
+ object->debug_id, cookie->def->name, object->events);
+
+ ret = fscache_alloc_object(cache, cookie->parent);
+ if (ret < 0)
+ goto error_put;
+
+ /* only attach if we managed to allocate all we needed, otherwise
+ * discard the object we just allocated and instead use the one
+ * attached to the cookie */
+ if (fscache_attach_object(cookie, object) < 0)
+ cache->ops->put_object(object);
+
+ _leave(" = 0");
+ return 0;
+
+object_already_extant:
+ ret = -ENOBUFS;
+ if (object->state >= FSCACHE_OBJECT_DYING) {
+ spin_unlock(&cookie->lock);
+ goto error;
+ }
+ spin_unlock(&cookie->lock);
+ _leave(" = 0 [found]");
+ return 0;
+
+error_put:
+ cache->ops->put_object(object);
+error:
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * attach a cache object to a cookie
+ */
+static int fscache_attach_object(struct fscache_cookie *cookie,
+ struct fscache_object *object)
+{
+ struct fscache_object *p;
+ struct fscache_cache *cache = object->cache;
+ struct hlist_node *_n;
+ int ret;
+
+ _enter("{%s},{OBJ%x}", cookie->def->name, object->debug_id);
+
+ spin_lock(&cookie->lock);
+
+ /* there may be multiple initial creations of this object, but we only
+ * want one */
+ ret = -EEXIST;
+ hlist_for_each_entry(p, _n, &cookie->backing_objects, cookie_link) {
+ if (p->cache == object->cache) {
+ if (p->state >= FSCACHE_OBJECT_DYING)
+ ret = -ENOBUFS;
+ goto cant_attach_object;
+ }
+ }
+
+ /* pin the parent object */
+ spin_lock_nested(&cookie->parent->lock, 1);
+ hlist_for_each_entry(p, _n, &cookie->parent->backing_objects,
+ cookie_link) {
+ if (p->cache == object->cache) {
+ if (p->state >= FSCACHE_OBJECT_DYING) {
+ ret = -ENOBUFS;
+ spin_unlock(&cookie->parent->lock);
+ goto cant_attach_object;
+ }
+ object->parent = p;
+ spin_lock(&p->lock);
+ p->n_children++;
+ spin_unlock(&p->lock);
+ break;
+ }
+ }
+ spin_unlock(&cookie->parent->lock);
+
+ /* attach to the cache's object list */
+ if (list_empty(&object->cache_link)) {
+ spin_lock(&cache->object_list_lock);
+ list_add(&object->cache_link, &cache->object_list);
+ spin_unlock(&cache->object_list_lock);
+ }
+
+ /* attach to the cookie */
+ object->cookie = cookie;
+ atomic_inc(&cookie->usage);
+ hlist_add_head(&object->cookie_link, &cookie->backing_objects);
+ ret = 0;
+
+cant_attach_object:
+ spin_unlock(&cookie->lock);
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * update the index entries backing a cookie
+ */
+void __fscache_update_cookie(struct fscache_cookie *cookie)
+{
+ struct fscache_object *object;
+ struct hlist_node *_p;
+
+ fscache_stat(&fscache_n_updates);
+
+ if (!cookie) {
+ fscache_stat(&fscache_n_updates_null);
+ _leave(" [no cookie]");
+ return;
+ }
+
+ _enter("{%s}", cookie->def->name);
+
+ BUG_ON(!cookie->def->get_aux);
+
+ spin_lock(&cookie->lock);
+
+ /* update the index entry on disk in each cache backing this cookie */
+ hlist_for_each_entry(object, _p,
+ &cookie->backing_objects, cookie_link) {
+ fscache_raise_event(object, FSCACHE_OBJECT_EV_UPDATE);
+ }
+
+ spin_unlock(&cookie->lock);
+ _leave("");
+}
+
+EXPORT_SYMBOL(__fscache_update_cookie);
+
+/*
+ * release a cookie back to the cache
+ * - the object will be marked as recyclable on disk if retire is true
+ * - all dependents of this cookie must have already been unregistered
+ * (indices/files/pages)
+ */
+void __fscache_relinquish_cookie(struct fscache_cookie *cookie, int retire)
+{
+ struct fscache_cache *cache;
+ struct fscache_object *object;
+ unsigned long event;
+
+ fscache_stat(&fscache_n_relinquishes);
+
+ if (!cookie) {
+ fscache_stat(&fscache_n_relinquishes_null);
+ _leave(" [no cookie]");
+ return;
+ }
+
+ _enter("%p{%s,%p},%d",
+ cookie, cookie->def->name, cookie->netfs_data, retire);
+
+ if (atomic_read(&cookie->n_children) != 0) {
+ printk(KERN_ERR "FS-Cache: Cookie '%s' still has children\n",
+ cookie->def->name);
+ BUG();
+ }
+
+ /* wait for the cookie to finish being instantiated (or to fail) */
+ if (test_bit(FSCACHE_COOKIE_CREATING, &cookie->flags)) {
+ fscache_stat(&fscache_n_relinquishes_waitcrt);
+ wait_on_bit(&cookie->flags, FSCACHE_COOKIE_CREATING,
+ fscache_wait_bit, TASK_UNINTERRUPTIBLE);
+ }
+
+ event = retire ? FSCACHE_OBJECT_EV_RETIRE : FSCACHE_OBJECT_EV_RELEASE;
+
+ /* detach pointers back to the netfs */
+ spin_lock(&cookie->lock);
+
+ cookie->netfs_data = NULL;
+ cookie->def = NULL;
+
+ /* break links with all the active objects */
+ while (!hlist_empty(&cookie->backing_objects)) {
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object,
+ cookie_link);
+
+ _debug("RELEASE OBJ%x", object->debug_id);
+
+ /* detach each cache object from the object cookie */
+ spin_lock(&object->lock);
+ hlist_del_init(&object->cookie_link);
+
+ cache = object->cache;
+ object->cookie = NULL;
+ fscache_raise_event(object, event);
+ spin_unlock(&object->lock);
+
+ if (atomic_dec_and_test(&cookie->usage))
+ /* the cookie refcount shouldn't be reduced to 0 yet */
+ BUG();
+ }
+
+ spin_unlock(&cookie->lock);
+
+ if (cookie->parent) {
+ ASSERTCMP(atomic_read(&cookie->parent->usage), >, 0);
+ ASSERTCMP(atomic_read(&cookie->parent->n_children), >, 0);
+ atomic_dec(&cookie->parent->n_children);
+ }
+
+ /* finally dispose of the cookie */
+ ASSERTCMP(atomic_read(&cookie->usage), >, 0);
+ fscache_cookie_put(cookie);
+
+ _leave("");
+}
+
+EXPORT_SYMBOL(__fscache_relinquish_cookie);
+
+/*
+ * destroy a cookie
+ */
+void __fscache_cookie_put(struct fscache_cookie *cookie)
+{
+ struct fscache_cookie *parent;
+
+ _enter("%p", cookie);
+
+ for (;;) {
+ _debug("FREE COOKIE %p", cookie);
+ parent = cookie->parent;
+ BUG_ON(!hlist_empty(&cookie->backing_objects));
+ kmem_cache_free(fscache_cookie_jar, cookie);
+
+ if (!parent)
+ break;
+
+ cookie = parent;
+ BUG_ON(atomic_read(&cookie->usage) <= 0);
+ if (!atomic_dec_and_test(&cookie->usage))
+ break;
+ }
+
+ _leave("");
+}
diff --git a/fs/fscache/fsc-fsdef.c b/fs/fscache/fsc-fsdef.c
new file mode 100644
index 0000000..7a0d1c0
--- /dev/null
+++ b/fs/fscache/fsc-fsdef.c
@@ -0,0 +1,111 @@
+/* Filesystem index definition
+ *
+ * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL CACHE
+#include <linux/module.h>
+#include "fsc-internal.h"
+
+static uint16_t fscache_fsdef_netfs_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax);
+
+static uint16_t fscache_fsdef_netfs_get_aux(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax);
+
+static fscache_checkaux_t fscache_fsdef_netfs_check_aux(void *cookie_netfs_data,
+ const void *data,
+ uint16_t datalen);
+
+struct fscache_cookie_def fscache_fsdef_netfs_def = {
+ .name = "FSDEF.netfs",
+ .type = FSCACHE_COOKIE_TYPE_INDEX,
+ .get_key = fscache_fsdef_netfs_get_key,
+ .get_aux = fscache_fsdef_netfs_get_aux,
+ .check_aux = fscache_fsdef_netfs_check_aux,
+};
+
+static struct fscache_cookie_def fscache_fsdef_index_def = {
+ .name = ".FS-Cache",
+ .type = FSCACHE_COOKIE_TYPE_INDEX,
+};
+
+struct fscache_cookie fscache_fsdef_index = {
+ .usage = ATOMIC_INIT(1),
+ .lock = SPIN_LOCK_UNLOCKED,
+ .backing_objects = HLIST_HEAD_INIT,
+ .def = &fscache_fsdef_index_def,
+};
+
+EXPORT_SYMBOL(fscache_fsdef_index);
+
+/*
+ * get the key data for an FSDEF index record
+ */
+static uint16_t fscache_fsdef_netfs_get_key(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
+{
+ const struct fscache_netfs *netfs = cookie_netfs_data;
+ unsigned klen;
+
+ _enter("{%s.%u},", netfs->name, netfs->version);
+
+ klen = strlen(netfs->name);
+ if (klen > bufmax)
+ return 0;
+
+ memcpy(buffer, netfs->name, klen);
+ return klen;
+}
+
+/*
+ * get the auxilliary data for an FSDEF index record
+ */
+static uint16_t fscache_fsdef_netfs_get_aux(const void *cookie_netfs_data,
+ void *buffer, uint16_t bufmax)
+{
+ const struct fscache_netfs *netfs = cookie_netfs_data;
+ unsigned dlen;
+
+ _enter("{%s.%u},", netfs->name, netfs->version);
+
+ dlen = sizeof(uint32_t);
+ if (dlen > bufmax)
+ return 0;
+
+ memcpy(buffer, &netfs->version, dlen);
+ return dlen;
+}
+
+/*
+ * check that the version stored in the auxilliary data is correct
+ */
+static fscache_checkaux_t fscache_fsdef_netfs_check_aux(void *cookie_netfs_data,
+ const void *data,
+ uint16_t datalen)
+{
+ struct fscache_netfs *netfs = cookie_netfs_data;
+ uint32_t version;
+
+ _enter("{%s},,%hu", netfs->name, datalen);
+
+ if (datalen != sizeof(version)) {
+ _leave(" = OBSOLETE [dl=%d v=%zu]", datalen, sizeof(version));
+ return FSCACHE_CHECKAUX_OBSOLETE;
+ }
+
+ memcpy(&version, data, sizeof(version));
+ if (version != netfs->version) {
+ _leave(" = OBSOLETE [ver=%x net=%x]", version, netfs->version);
+ return FSCACHE_CHECKAUX_OBSOLETE;
+ }
+
+ _leave(" = OKAY");
+ return FSCACHE_CHECKAUX_OKAY;
+}
diff --git a/fs/fscache/fsc-internal.h b/fs/fscache/fsc-internal.h
new file mode 100644
index 0000000..875b8a9
--- /dev/null
+++ b/fs/fscache/fsc-internal.h
@@ -0,0 +1,376 @@
+/* Internal definitions for FS-Cache
+ *
+ * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * Lock order, in the order in which multiple locks should be obtained:
+ * - fscache_addremove_sem
+ * - cookie->lock
+ * - cookie->parent->lock
+ * - cache->object_list_lock
+ * - object->lock
+ * - object->parent->lock
+ * - fscache_thread_lock
+ *
+ */
+
+#include <linux/fscache-cache.h>
+#include <linux/sched.h>
+
+#define FSCACHE_MIN_THREADS 4
+#define FSCACHE_MAX_THREADS 32
+
+/*
+ * fsc-cache.c
+ */
+extern struct list_head fscache_cache_list;
+extern struct rw_semaphore fscache_addremove_sem;
+extern wait_queue_head_t fscache_clearance_wq;
+
+extern struct fscache_cache *fscache_select_cache_for_object(
+ struct fscache_cookie *);
+
+/*
+ * fsc-cookie.c
+ */
+extern struct kmem_cache *fscache_cookie_jar;
+
+extern void fscache_cookie_init_once(void *, struct kmem_cache *,
+ unsigned long);
+extern void __fscache_cookie_put(struct fscache_cookie *);
+
+/*
+ * fsc-fsdef.c
+ */
+extern struct fscache_cookie fscache_fsdef_index;
+extern struct fscache_cookie_def fscache_fsdef_netfs_def;
+
+/*
+ * fsc-main.c
+ */
+extern unsigned fscache_defer_lookup;
+extern unsigned fscache_defer_create;
+extern unsigned fscache_debug;
+extern struct kset fscache_kset;
+
+extern int fscache_wait_bit(void *);
+extern int fscache_wait_bit_interruptible(void *);
+
+/*
+ * fsc-object.c
+ */
+extern void fscache_object_state_machine(struct fscache_object *);
+extern void fscache_withdrawing_object(struct fscache_cache *,
+ struct fscache_object *);
+
+/*
+ * fsc-stats.c
+ */
+#ifdef CONFIG_FSCACHE_STATS
+extern atomic_t fscache_n_ops_processed[FSCACHE_MAX_THREADS];
+extern atomic_t fscache_n_objs_processed[FSCACHE_MAX_THREADS];
+
+extern atomic_t fscache_n_op_pend;
+extern atomic_t fscache_n_op_run;
+extern atomic_t fscache_n_op_enqueue;
+extern atomic_t fscache_n_op_requeue;
+extern atomic_t fscache_n_op_release;
+
+extern atomic_t fscache_n_attr_changed;
+extern atomic_t fscache_n_attr_changed_ok;
+extern atomic_t fscache_n_attr_changed_nobufs;
+extern atomic_t fscache_n_attr_changed_nomem;
+extern atomic_t fscache_n_attr_changed_calls;
+
+extern atomic_t fscache_n_allocs;
+extern atomic_t fscache_n_allocs_ok;
+extern atomic_t fscache_n_allocs_wait;
+extern atomic_t fscache_n_allocs_nobufs;
+extern atomic_t fscache_n_alloc_ops;
+extern atomic_t fscache_n_alloc_op_waits;
+
+extern atomic_t fscache_n_retrievals;
+extern atomic_t fscache_n_retrievals_ok;
+extern atomic_t fscache_n_retrievals_wait;
+extern atomic_t fscache_n_retrievals_nodata;
+extern atomic_t fscache_n_retrievals_nobufs;
+extern atomic_t fscache_n_retrievals_intr;
+extern atomic_t fscache_n_retrievals_nomem;
+extern atomic_t fscache_n_retrieval_ops;
+extern atomic_t fscache_n_retrieval_op_waits;
+
+extern atomic_t fscache_n_stores;
+extern atomic_t fscache_n_stores_ok;
+extern atomic_t fscache_n_stores_again;
+extern atomic_t fscache_n_stores_nobufs;
+extern atomic_t fscache_n_stores_oom;
+extern atomic_t fscache_n_store_ops;
+extern atomic_t fscache_n_store_calls;
+
+extern atomic_t fscache_n_marks;
+extern atomic_t fscache_n_uncaches;
+
+extern atomic_t fscache_n_acquires;
+extern atomic_t fscache_n_acquires_null;
+extern atomic_t fscache_n_acquires_no_cache;
+extern atomic_t fscache_n_acquires_ok;
+extern atomic_t fscache_n_acquires_nobufs;
+extern atomic_t fscache_n_acquires_oom;
+
+extern atomic_t fscache_n_updates;
+extern atomic_t fscache_n_updates_null;
+extern atomic_t fscache_n_updates_run;
+
+extern atomic_t fscache_n_relinquishes;
+extern atomic_t fscache_n_relinquishes_null;
+extern atomic_t fscache_n_relinquishes_waitcrt;
+
+extern atomic_t fscache_n_cookie_index;
+extern atomic_t fscache_n_cookie_data;
+extern atomic_t fscache_n_cookie_special;
+
+extern atomic_t fscache_n_object_alloc;
+extern atomic_t fscache_n_object_no_alloc;
+extern atomic_t fscache_n_object_lookups;
+extern atomic_t fscache_n_object_lookups_negative;
+extern atomic_t fscache_n_object_lookups_positive;
+extern atomic_t fscache_n_object_created;
+extern atomic_t fscache_n_object_avail;
+extern atomic_t fscache_n_object_boosted;
+
+static inline void fscache_stat(atomic_t *stat)
+{
+ atomic_inc(stat);
+}
+#else
+
+#define fscache_stat(stat) do {} while(0)
+#endif
+
+#ifdef CONFIG_FSCACHE_HISTOGRAM
+extern atomic_t fscache_obj_instantiate_histogram[HZ];
+extern atomic_t fscache_objs_histogram[HZ];
+extern atomic_t fscache_ops_histogram[HZ];
+extern atomic_t fscache_retrieval_delay_histogram[HZ];
+extern atomic_t fscache_retrieval_histogram[HZ];
+
+static inline void fscache_hist(atomic_t histogram[], unsigned long start_jif)
+{
+ unsigned long jif = jiffies - start_jif;
+ if (jif >= HZ)
+ jif = HZ - 1;
+ atomic_inc(&histogram[jif]);
+}
+
+#else
+#define fscache_hist(hist, start_jif) do {} while(0)
+#endif
+
+#ifdef CONFIG_FSCACHE_PROC
+extern int __init fscache_proc_init(void);
+extern void fscache_proc_cleanup(void);
+#else
+#define fscache_proc_init() (0)
+#define fscache_proc_cleanup() do {} while(0)
+#endif
+
+/*
+ * fsc-threads.c
+ */
+extern void fscache_start_operations(struct fscache_object *);
+extern void fscache_enqueue_object(struct fscache_object *);
+extern void fscache_enqueue_dependents(struct fscache_object *);
+extern void fscache_dequeue_object(struct fscache_object *);
+extern void fscache_boost_object(struct fscache_object *);
+extern int fscache_init_threads(void);
+extern void fscache_kill_threads(void);
+
+/*
+ * raise an event on an object
+ * - if the event is not masked for that object, then the object is
+ * queued for attention by the thread pool.
+ */
+static inline void fscache_raise_event(struct fscache_object *object,
+ unsigned event)
+{
+ if (!test_and_set_bit(event, &object->events) &&
+ test_bit(event, &object->event_mask))
+ fscache_enqueue_object(object);
+}
+
+/*
+ * drop a reference to a cookie
+ */
+static inline void fscache_cookie_put(struct fscache_cookie *cookie)
+{
+ BUG_ON(atomic_read(&cookie->usage) <= 0);
+ if (atomic_dec_and_test(&cookie->usage))
+ __fscache_cookie_put(cookie);
+}
+
+/*
+ * get an extra reference to a netfs retrieval context
+ */
+static inline
+void *fscache_get_context(struct fscache_cookie *cookie, void *context)
+{
+ if (cookie->def->get_context)
+ cookie->def->get_context(cookie->netfs_data, context);
+ return context;
+}
+
+/*
+ * release a reference to a netfs retrieval context
+ */
+static inline
+void fscache_put_context(struct fscache_cookie *cookie, void *context)
+{
+ if (cookie->def->put_context)
+ cookie->def->put_context(cookie->netfs_data, context);
+}
+
+/*****************************************************************************/
+/*
+ * debug tracing
+ */
+#define dbgprintk(FMT,...) \
+ printk("[%-6.6s] "FMT"\n", current->comm ,##__VA_ARGS__)
+
+/* make sure we maintain the format strings, even when debugging is disabled */
+static inline __attribute__((format(printf, 1, 2)))
+void _dbprintk(const char *fmt, ...)
+{
+}
+
+#define kenter(FMT, ...) dbgprintk("==> %s("FMT")", __FUNCTION__ ,##__VA_ARGS__)
+#define kleave(FMT, ...) dbgprintk("<== %s()"FMT"", __FUNCTION__ ,##__VA_ARGS__)
+#define kdebug(FMT, ...) dbgprintk(FMT ,##__VA_ARGS__)
+
+#define kjournal(FMT, ...) _dbprintk(FMT ,##__VA_ARGS__)
+
+#ifdef __KDEBUG
+#define _enter(FMT, ...) kenter(FMT ,##__VA_ARGS__)
+#define _leave(FMT, ...) kleave(FMT ,##__VA_ARGS__)
+#define _debug(FMT, ...) kdebug(FMT ,##__VA_ARGS__)
+
+#elif defined(CONFIG_FSCACHE_DEBUG)
+#define _enter(FMT, ...) \
+do { \
+ if (__do_kdebug(ENTER)) \
+ kenter(FMT ,##__VA_ARGS__); \
+} while(0)
+
+#define _leave(FMT, ...) \
+do { \
+ if (__do_kdebug(LEAVE)) \
+ kleave(FMT ,##__VA_ARGS__); \
+} while(0)
+
+#define _debug(FMT, ...) \
+do { \
+ if (__do_kdebug(DEBUG)) \
+ kdebug(FMT ,##__VA_ARGS__); \
+} while(0)
+
+#else
+#define _enter(FMT,...) _dbprintk("==> %s("FMT")",__FUNCTION__ ,##__VA_ARGS__)
+#define _leave(FMT,...) _dbprintk("<== %s()"FMT"",__FUNCTION__ ,##__VA_ARGS__)
+#define _debug(FMT,...) _dbprintk(FMT ,##__VA_ARGS__)
+#endif
+
+/*
+ * determine whether a particular optional debugging point should be logged
+ * - we need to go through three steps to persuade cpp to correctly join the
+ * shorthand in FSCACHE_DEBUG_LEVEL with its prefix
+ */
+#define ____do_kdebug(LEVEL, POINT) \
+ unlikely((fscache_debug & (FSCACHE_POINT_##POINT << (FSCACHE_DEBUG_ ## LEVEL * 3))))
+#define ___do_kdebug(LEVEL, POINT) \
+ ____do_kdebug(LEVEL, POINT)
+#define __do_kdebug(POINT) \
+ ___do_kdebug(FSCACHE_DEBUG_LEVEL, POINT)
+
+#define FSCACHE_DEBUG_CACHE 0
+#define FSCACHE_DEBUG_COOKIE 1
+#define FSCACHE_DEBUG_PAGE 2
+#define FSCACHE_DEBUG_THREAD 3
+
+#define FSCACHE_POINT_ENTER 1
+#define FSCACHE_POINT_LEAVE 2
+#define FSCACHE_POINT_DEBUG 4
+
+#ifndef FSCACHE_DEBUG_LEVEL
+#define FSCACHE_DEBUG_LEVEL CACHE
+#endif
+
+/*
+ * assertions
+ */
+#if 1 // defined(__KDEBUGALL)
+
+#define ASSERT(X) \
+do { \
+ if (unlikely(!(X))) { \
+ printk(KERN_ERR "\n"); \
+ printk(KERN_ERR "FS-Cache: Assertion failed\n"); \
+ BUG(); \
+ } \
+} while(0)
+
+#define ASSERTCMP(X, OP, Y) \
+do { \
+ if (unlikely(!((X) OP (Y)))) { \
+ printk(KERN_ERR "\n"); \
+ printk(KERN_ERR "FS-Cache: Assertion failed\n"); \
+ printk(KERN_ERR "%lx " #OP " %lx is false\n", \
+ (unsigned long)(X), (unsigned long)(Y)); \
+ BUG(); \
+ } \
+} while(0)
+
+#define ASSERTIF(C, X) \
+do { \
+ if (unlikely((C) && !(X))) { \
+ printk(KERN_ERR "\n"); \
+ printk(KERN_ERR "FS-Cache: Assertion failed\n"); \
+ BUG(); \
+ } \
+} while(0)
+
+#define ASSERTIFCMP(C, X, OP, Y) \
+do { \
+ if (unlikely((C) && !((X) OP (Y)))) { \
+ printk(KERN_ERR "\n"); \
+ printk(KERN_ERR "FS-Cache: Assertion failed\n"); \
+ printk(KERN_ERR "%lx " #OP " %lx is false\n", \
+ (unsigned long)(X), (unsigned long)(Y)); \
+ BUG(); \
+ } \
+} while(0)
+
+#else
+
+#define ASSERT(X) \
+do { \
+} while(0)
+
+#define ASSERTCMP(X, OP, Y) \
+do { \
+} while(0)
+
+#define ASSERTIF(C, X) \
+do { \
+} while(0)
+
+#define ASSERTIFCMP(C, X, OP, Y) \
+do { \
+} while(0)
+
+#endif /* assert or not */
diff --git a/fs/fscache/fsc-main.c b/fs/fscache/fsc-main.c
new file mode 100644
index 0000000..fc19117
--- /dev/null
+++ b/fs/fscache/fsc-main.c
@@ -0,0 +1,127 @@
+/* General filesystem local caching manager
+ *
+ * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL CACHE
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include "fsc-internal.h"
+
+MODULE_DESCRIPTION("FS Cache Manager");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+unsigned fscache_defer_lookup = 1;
+module_param_named(defer_lookup, fscache_defer_lookup, uint, S_IWUSR | S_IRUGO);
+MODULE_PARM_DESC(fscache_defer_lookup, "Defer cookie lookup to background thread");
+
+unsigned fscache_defer_create = 1;
+module_param_named(defer_create, fscache_defer_create, uint, S_IWUSR | S_IRUGO);
+MODULE_PARM_DESC(fscache_defer_create, "Defer cookie creation to background thread");
+
+unsigned fscache_debug;
+module_param_named(debug, fscache_debug, uint, S_IWUSR | S_IRUGO);
+MODULE_PARM_DESC(fscache_debug, "FS-Cache debugging mask");
+
+static struct sysfs_ops fscache_sysfs_ops = {
+};
+
+static struct kobj_type fscache_ktype = {
+ .sysfs_ops = &fscache_sysfs_ops,
+};
+
+struct kset fscache_kset = {
+ .kobj.name = "fscache",
+ .kobj.kset = &fs_subsys,
+ .ktype = &fscache_ktype,
+};
+
+/*
+ * initialise the fs caching module
+ */
+static int __init fscache_init(void)
+{
+ int ret;
+
+ ret = fscache_proc_init();
+ if (ret < 0)
+ goto error_proc;
+
+ ret = fscache_init_threads();
+ if (ret < 0)
+ goto error_init_threads;
+
+ fscache_cookie_jar = kmem_cache_create("fscache_cookie_jar",
+ sizeof(struct fscache_cookie),
+ 0,
+ 0,
+ fscache_cookie_init_once);
+ if (!fscache_cookie_jar) {
+ printk(KERN_NOTICE
+ "FS-Cache: Failed to allocate a cookie jar\n");
+ ret = -ENOMEM;
+ goto error_cookie_jar;
+ }
+
+ ret = kset_register(&fscache_kset);
+ if (ret < 0)
+ goto error_kset;
+
+ printk(KERN_NOTICE "FS-Cache: Loaded\n");
+ return 0;
+
+error_kset:
+ kmem_cache_destroy(fscache_cookie_jar);
+error_cookie_jar:
+ fscache_kill_threads();
+error_init_threads:
+ fscache_proc_cleanup();
+error_proc:
+ return ret;
+}
+
+fs_initcall(fscache_init);
+
+/*
+ * clean up on module removal
+ */
+static void __exit fscache_exit(void)
+{
+ _enter("");
+
+ kset_unregister(&fscache_kset);
+ kmem_cache_destroy(fscache_cookie_jar);
+ fscache_kill_threads();
+ fscache_proc_cleanup();
+ printk(KERN_NOTICE "FS-Cache: Unloaded\n");
+}
+
+module_exit(fscache_exit);
+
+/*
+ * wait_on_bit() sleep function for uninterruptible waiting
+ */
+int fscache_wait_bit(void *flags)
+{
+ schedule();
+ return 0;
+}
+
+/*
+ * wait_on_bit() sleep function for interruptible waiting
+ */
+int fscache_wait_bit_interruptible(void *flags)
+{
+ schedule();
+ return signal_pending(current);
+}
diff --git a/fs/fscache/fsc-manage.c b/fs/fscache/fsc-manage.c
new file mode 100644
index 0000000..42fef97
--- /dev/null
+++ b/fs/fscache/fsc-manage.c
@@ -0,0 +1,260 @@
+/* Manage cache objects
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL PAGE
+#include <linux/module.h>
+#include <linux/fscache-cache.h>
+#include <linux/buffer_head.h>
+#include <linux/pagevec.h>
+#include "fsc-internal.h"
+
+/*
+ * wait for a slow operation to float to the front of the op queue, then
+ * effectively suspend processing on this object till the calling process
+ * context has completed the operation
+ * - this prevents kfscached being hogged by a really slow op
+ */
+#if 0
+static int fscache_slow_op(struct fscache_object *object,
+ struct fscache_operation *op)
+{
+ int ret;
+
+ kenter("");
+
+ if (test_and_clear_bit(FSCACHE_OP_WAITING, &op->flags))
+ wake_up_bit(&op->flags, FSCACHE_OP_WAITING);
+
+ if (test_bit(FSCACHE_OP_IN_PROGRESS, &op->flags))
+ ret = -EINPROGRESS;
+ else
+ ret = 0;
+
+ kleave(" = %d", ret);
+ return ret;
+}
+#endif
+
+/*
+ * reserve space for an object
+ */
+int __fscache_reserve_space(struct fscache_cookie *cookie, loff_t size)
+{
+#if 0
+ struct fscache_operation *op;
+ struct fscache_object *object;
+ int ret;
+
+ _enter("%p,%llu,", cookie, size);
+
+ ASSERTCMP(cookie->def->type, !=, FSCACHE_COOKIE_TYPE_INDEX);
+
+ op = kzalloc(sizeof(*op), GFP_KERNEL);
+ if (!op) {
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+ }
+
+ op->processor = fscache_slow_op;
+ __set_bit(FSCACHE_OP_WAITING, &op->flags);
+ __set_bit(FSCACHE_OP_IN_PROGRESS, &op->flags);
+
+ spin_lock(&cookie->lock);
+
+ ret = -ENOBUFS;
+ if (hlist_empty(&cookie->backing_objects))
+ goto error;
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+ if (test_bit(FSCACHE_IOERROR, &object->cache->flags))
+ goto error;
+
+ ret = -EOPNOTSUPP;
+ if (!object->cache->ops->reserve_space)
+ goto error;
+
+ /* prevent the file from being uncached whilst we access it and exclude
+ * write attempts on pages
+ */
+ ret = -ENOBUFS;
+ if (!object->cache->ops->grab_object(object))
+ goto error;
+ list_add_tail(&op->link, &object->operations);
+
+ if (!test_and_set_bit(FSCACHE_OBJECT_BUSY, &object->flags))
+ queue_work(fscache_workqueue, &object->work);
+
+ spin_unlock(&cookie->lock);
+
+ /* wait for the operation queue to be whittled down */
+ if (wait_on_bit(&op->flags, FSCACHE_OP_WAITING,
+ fscache_wait_bit_interruptible, TASK_INTERRUPTIBLE
+ ) < 0) {
+ /* we were interrupted */
+ spin_lock(&cookie->lock);
+ if (!test_and_clear_bit(FSCACHE_OP_WAITING, &op->flags))
+ queue_work(fscache_workqueue, &object->work);
+ if (!test_and_clear_bit(FSCACHE_OP_IN_PROGRESS, &op->flags))
+ BUG();
+ ret = -ERESTARTSYS;
+ goto out_locked;
+ }
+
+ /* okay - we're now in a position to make a reservation */
+ ret = -ENOBUFS;
+ if (test_bit(FSCACHE_OBJECT_ABORTED, &object->flags) ||
+ test_bit(FSCACHE_IOERROR, &object->cache->flags))
+ goto out;
+
+ ret = -ERESTARTSYS;
+ if (signal_pending(current))
+ goto out;
+
+ /* ask the cache to honour the operation */
+ ret = object->cache->ops->reserve_space(object, size);
+
+out:
+ spin_lock(&cookie->lock);
+out_locked:
+ if (!test_and_clear_bit(FSCACHE_OP_IN_PROGRESS, &op->flags))
+ BUG();
+ queue_work(fscache_workqueue, &object->work);
+ spin_unlock(&cookie->lock);
+
+ kleave(" = %d", ret);
+ return ret;
+
+error:
+ spin_unlock(&cookie->lock);
+ kfree(op);
+ kleave(" = %d", ret);
+ return ret;
+#endif
+ kleave(" = -ENOBUFS");
+ return -ENOBUFS;
+}
+
+EXPORT_SYMBOL(__fscache_reserve_space);
+
+#if 0
+/*
+ * pin an object into the cache
+ */
+int __fscache_pin_cookie(struct fscache_cookie *cookie)
+{
+ struct fscache_object *object;
+ int ret;
+
+ _enter("%p", cookie);
+
+ if (hlist_empty(&cookie->backing_objects)) {
+ _leave(" = -ENOBUFS");
+ return -ENOBUFS;
+ }
+
+ /* not supposed to use this for indexes */
+ BUG_ON(cookie->def->type == FSCACHE_COOKIE_TYPE_INDEX);
+
+ /* prevent the file from being uncached whilst we access it and exclude
+ * read and write attempts on pages
+ */
+ down_write(&cookie->sem);
+
+ ret = -ENOBUFS;
+ if (!hlist_empty(&cookie->backing_objects)) {
+ /* get and pin the backing object */
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+
+ if (test_bit(FSCACHE_IOERROR, &object->cache->flags))
+ goto out;
+
+ if (!object->cache->ops->pin_object) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ /* prevent the cache from being withdrawn */
+ if (fscache_operation_lock(object)) {
+ if (object->cache->ops->grab_object(object)) {
+ /* ask the cache to honour the operation */
+ ret = object->cache->ops->pin_object(object);
+
+ object->cache->ops->put_object(object);
+ }
+
+ fscache_operation_unlock(object);
+ }
+ }
+
+out:
+ up_write(&cookie->sem);
+ _leave(" = %d", ret);
+ return ret;
+}
+
+EXPORT_SYMBOL(__fscache_pin_cookie);
+
+/*
+ * unpin an object into the cache
+ */
+void __fscache_unpin_cookie(struct fscache_cookie *cookie)
+{
+ struct fscache_object *object;
+ int ret;
+
+ _enter("%p", cookie);
+
+ if (hlist_empty(&cookie->backing_objects)) {
+ _leave(" [no obj]");
+ return;
+ }
+
+ /* not supposed to use this for indexes */
+ BUG_ON(cookie->def->type == FSCACHE_COOKIE_TYPE_INDEX);
+
+ /* prevent the file from being uncached whilst we access it and exclude
+ * read and write attempts on pages
+ */
+ down_write(&cookie->sem);
+
+ ret = -ENOBUFS;
+ if (!hlist_empty(&cookie->backing_objects)) {
+ /* get and unpin the backing object */
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+
+ if (test_bit(FSCACHE_IOERROR, &object->cache->flags))
+ goto out;
+
+ if (!object->cache->ops->unpin_object)
+ goto out;
+
+ /* prevent the cache from being withdrawn */
+ if (fscache_operation_lock(object)) {
+ if (object->cache->ops->grab_object(object)) {
+ /* ask the cache to honour the operation */
+ object->cache->ops->unpin_object(object);
+
+ object->cache->ops->put_object(object);
+ }
+
+ fscache_operation_unlock(object);
+ }
+ }
+
+out:
+ up_write(&cookie->sem);
+ _leave("");
+}
+
+EXPORT_SYMBOL(__fscache_unpin_cookie);
+#endif
diff --git a/fs/fscache/fsc-object.c b/fs/fscache/fsc-object.c
new file mode 100644
index 0000000..e25d377
--- /dev/null
+++ b/fs/fscache/fsc-object.c
@@ -0,0 +1,586 @@
+/* FS-Cache object state machine handler
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL COOKIE
+#include <linux/module.h>
+#include "fsc-internal.h"
+
+const char *fscache_object_states[] = {
+ [FSCACHE_OBJECT_INIT] = "OBJECT_INIT",
+ [FSCACHE_OBJECT_LOOKING_UP] = "OBJECT_LOOKING_UP",
+ [FSCACHE_OBJECT_CREATING] = "OBJECT_CREATING",
+ [FSCACHE_OBJECT_AVAILABLE] = "OBJECT_AVAILABLE",
+ [FSCACHE_OBJECT_ACTIVE] = "OBJECT_ACTIVE",
+ [FSCACHE_OBJECT_UPDATING] = "OBJECT_UPDATING",
+ [FSCACHE_OBJECT_DYING] = "OBJECT_DYING",
+ [FSCACHE_OBJECT_LC_DYING] = "OBJECT_LC_DYING",
+ [FSCACHE_OBJECT_ABORT_INIT] = "OBJECT_ABORT_INIT",
+ [FSCACHE_OBJECT_RELEASING] = "OBJECT_RELEASING",
+ [FSCACHE_OBJECT_RECYCLING] = "OBJECT_RECYCLING",
+ [FSCACHE_OBJECT_WITHDRAWING] = "OBJECT_WITHDRAWING",
+ [FSCACHE_OBJECT_DEAD] = "OBJECT_DEAD",
+};
+
+EXPORT_SYMBOL(fscache_object_states);
+
+static void fscache_check_object_parent(struct fscache_object *);
+static void fscache_lookup_object(struct fscache_object *);
+static void fscache_object_available(struct fscache_object *);
+static void fscache_release_object(struct fscache_object *);
+static void fscache_withdraw_object(struct fscache_object *);
+
+/*
+ * object state machine processor
+ * - initiates parent lookup
+ * - does object lookup
+ * - does object creation
+ * - does object recycling and retirement
+ * - does object withdrawal
+ */
+void fscache_object_state_machine(struct fscache_object *object)
+{
+ ASSERT(object != NULL);
+
+ _enter("{OBJ%x,%s,%lx}",
+ object->debug_id, fscache_object_states[object->state],
+ object->events);
+
+ switch (object->state) {
+ case FSCACHE_OBJECT_INIT:
+ object->event_mask =
+ ULONG_MAX & ~(1 << FSCACHE_OBJECT_EV_CLEARED);
+ fscache_check_object_parent(object);
+ goto done;
+
+ case FSCACHE_OBJECT_LOOKING_UP:
+ fscache_lookup_object(object);
+ goto lookup_transit;
+
+ case FSCACHE_OBJECT_CREATING:
+ fscache_lookup_object(object);
+ goto lookup_transit;
+
+ case FSCACHE_OBJECT_AVAILABLE:
+ fscache_object_available(object);
+ goto active_transit;
+
+ case FSCACHE_OBJECT_ACTIVE:
+ goto active_transit;
+
+ case FSCACHE_OBJECT_UPDATING:
+ clear_bit(FSCACHE_OBJECT_EV_UPDATE, &object->events);
+ fscache_stat(&fscache_n_updates_run);
+ object->cache->ops->update_object(object);
+ goto active_transit;
+
+ /* object started dying during lookup */
+ case FSCACHE_OBJECT_LC_DYING:
+ object->event_mask &= ~(1 << FSCACHE_OBJECT_EV_UPDATE);
+ object->cache->ops->lookup_complete(object);
+
+ object->state = FSCACHE_OBJECT_DYING;
+ spin_lock(&object->lock);
+ if (test_and_clear_bit(FSCACHE_COOKIE_CREATING,
+ &object->cookie->flags))
+ wake_up_bit(&object->cookie->flags,
+ FSCACHE_COOKIE_CREATING);
+ spin_unlock(&object->lock);
+
+ /* wait for completion of active accessors */
+ case FSCACHE_OBJECT_DYING:
+ dying:
+ clear_bit(FSCACHE_OBJECT_EV_CLEARED, &object->events);
+ spin_lock(&object->lock);
+ _debug("dying OBJ%x {%d,%d}",
+ object->debug_id, object->n_ops, object->n_children);
+ if (object->n_ops == 0 && object->n_children == 0) {
+ object->event_mask &=
+ ~(1 << FSCACHE_OBJECT_EV_CLEARED);
+ object->event_mask |=
+ (1 << FSCACHE_OBJECT_EV_WITHDRAW) |
+ (1 << FSCACHE_OBJECT_EV_RETIRE) |
+ (1 << FSCACHE_OBJECT_EV_RELEASE) |
+ (1 << FSCACHE_OBJECT_EV_ERROR);
+ } else {
+ object->event_mask &=
+ ~((1 << FSCACHE_OBJECT_EV_WITHDRAW) |
+ (1 << FSCACHE_OBJECT_EV_RETIRE) |
+ (1 << FSCACHE_OBJECT_EV_RELEASE) |
+ (1 << FSCACHE_OBJECT_EV_ERROR));
+ object->event_mask |=
+ 1 << FSCACHE_OBJECT_EV_CLEARED;
+ }
+ spin_unlock(&object->lock);
+ fscache_enqueue_dependents(object);
+ goto terminal_transit;
+
+ /* handle an abort during the init state */
+ case FSCACHE_OBJECT_ABORT_INIT:
+ _debug("handle abort init %lx", object->events);
+ object->event_mask &= ~(1 << FSCACHE_OBJECT_EV_UPDATE);
+ fscache_dequeue_object(object);
+
+ object->state = FSCACHE_OBJECT_DYING;
+ spin_lock(&object->lock);
+ if (test_and_clear_bit(FSCACHE_COOKIE_CREATING,
+ &object->cookie->flags))
+ wake_up_bit(&object->cookie->flags,
+ FSCACHE_COOKIE_CREATING);
+ spin_unlock(&object->lock);
+ goto dying;
+
+ case FSCACHE_OBJECT_RELEASING:
+ case FSCACHE_OBJECT_RECYCLING:
+ object->event_mask &=
+ ~((1 << FSCACHE_OBJECT_EV_WITHDRAW) |
+ (1 << FSCACHE_OBJECT_EV_RETIRE) |
+ (1 << FSCACHE_OBJECT_EV_RELEASE) |
+ (1 << FSCACHE_OBJECT_EV_ERROR));
+ fscache_release_object(object);
+ object->state = FSCACHE_OBJECT_DEAD;
+ goto terminal_transit;
+
+ case FSCACHE_OBJECT_WITHDRAWING:
+ object->event_mask &=
+ ~((1 << FSCACHE_OBJECT_EV_WITHDRAW) |
+ (1 << FSCACHE_OBJECT_EV_RETIRE) |
+ (1 << FSCACHE_OBJECT_EV_RELEASE) |
+ (1 << FSCACHE_OBJECT_EV_ERROR));
+ fscache_withdraw_object(object);
+ object->state = FSCACHE_OBJECT_DEAD;
+ goto terminal_transit;
+
+ case FSCACHE_OBJECT_DEAD:
+ printk(KERN_ERR "FS-Cache:"
+ " Unexpected event in dead state %lx\n",
+ object->events & object->event_mask);
+ BUG();
+
+ default:
+ printk(KERN_ERR "FS-Cache: Unknown object state %u\n",
+ object->state);
+ BUG();
+ }
+
+ /* determine the transition from a lookup state */
+lookup_transit:
+ switch (fls(object->events & object->event_mask) - 1) {
+ case FSCACHE_OBJECT_EV_WITHDRAW:
+ case FSCACHE_OBJECT_EV_RETIRE:
+ case FSCACHE_OBJECT_EV_RELEASE:
+ case FSCACHE_OBJECT_EV_ERROR:
+ object->state = FSCACHE_OBJECT_LC_DYING;
+ break;
+ case FSCACHE_OBJECT_EV_REQUEUE:
+ break;
+ case -1:
+ break; /* sleep until event */
+ default:
+ goto unsupported_event;
+ }
+ goto done;
+
+ /* determine the transition from an active state */
+active_transit:
+ switch (fls(object->events & object->event_mask) - 1) {
+ case FSCACHE_OBJECT_EV_WITHDRAW:
+ case FSCACHE_OBJECT_EV_RETIRE:
+ case FSCACHE_OBJECT_EV_RELEASE:
+ case FSCACHE_OBJECT_EV_ERROR:
+ object->state = FSCACHE_OBJECT_DYING;
+ break;
+ case FSCACHE_OBJECT_EV_UPDATE:
+ object->state = FSCACHE_OBJECT_UPDATING;
+ break;
+ case -1:
+ object->state = FSCACHE_OBJECT_ACTIVE;
+ break; /* sleep until event */
+ default:
+ goto unsupported_event;
+ }
+ goto done;
+
+ /* determine the transition from a terminal state */
+terminal_transit:
+ switch (fls(object->events & object->event_mask) - 1) {
+ case FSCACHE_OBJECT_EV_WITHDRAW:
+ object->state = FSCACHE_OBJECT_WITHDRAWING;
+ break;
+ case FSCACHE_OBJECT_EV_RETIRE:
+ object->state = FSCACHE_OBJECT_RECYCLING;
+ break;
+ case FSCACHE_OBJECT_EV_RELEASE:
+ object->state = FSCACHE_OBJECT_RELEASING;
+ break;
+ case FSCACHE_OBJECT_EV_ERROR:
+ object->state = FSCACHE_OBJECT_WITHDRAWING;
+ break;
+ case FSCACHE_OBJECT_EV_CLEARED:
+ object->state = FSCACHE_OBJECT_DYING;
+ break;
+ case -1:
+ break; /* sleep until event */
+ default:
+ goto unsupported_event;
+ }
+
+done:
+ _leave(" [->%s]", fscache_object_states[object->state]);
+ return;
+
+unsupported_event:
+ printk(KERN_ERR "FS-Cache:"
+ " Unsupported event %lx [mask %lx] in state %s\n",
+ object->events, object->event_mask,
+ fscache_object_states[object->state]);
+ BUG();
+}
+
+/*
+ * check the specified object's parent to see if we can make use of it
+ * immediately to do a creation
+ * - we may need to start the process of creating a parent and we need to wait
+ * for the parent's lookup and creation to complete if it's not there yet
+ * - an object's cookie is pinned until we clear FSCACHE_COOKIE_CREATING on the
+ * leaf-most cookies of the object and all its children
+ */
+static void fscache_check_object_parent(struct fscache_object *object)
+{
+ struct fscache_object *parent;
+
+ _enter("");
+ ASSERT(object->cookie != NULL);
+ ASSERT(object->cookie->parent != NULL);
+ ASSERT(list_empty(&object->work_link));
+
+ if (object->events & ((1 << FSCACHE_OBJECT_EV_ERROR) |
+ (1 << FSCACHE_OBJECT_EV_RELEASE) |
+ (1 << FSCACHE_OBJECT_EV_RETIRE) |
+ (1 << FSCACHE_OBJECT_EV_WITHDRAW))) {
+ _debug("abort init %lx", object->events);
+ object->state = FSCACHE_OBJECT_ABORT_INIT;
+ return;
+ }
+
+ spin_lock(&object->cookie->lock);
+ spin_lock_nested(&object->cookie->parent->lock, 1);
+
+ parent = object->parent;
+ if (!parent) {
+ _debug("no parent");
+ set_bit(FSCACHE_OBJECT_EV_WITHDRAW, &object->events);
+ } else {
+ spin_lock_nested(&parent->lock, 1);
+ _debug("parent %s", fscache_object_states[parent->state]);
+
+ if (parent->state >= FSCACHE_OBJECT_DYING) {
+ _debug("bad parent");
+ set_bit(FSCACHE_OBJECT_EV_WITHDRAW, &object->events);
+ } else if (parent->state < FSCACHE_OBJECT_AVAILABLE) {
+ _debug("wait");
+ object->cache->ops->grab_object(object);
+ set_bit(FSCACHE_OBJECT_WAITING, &object->flags);
+ list_add(&object->work_link, &parent->dependents);
+ atomic_inc(&object->cache->thread_usage);
+ if (parent->state == FSCACHE_OBJECT_INIT)
+ fscache_enqueue_object(parent);
+ } else {
+ _debug("go");
+ parent->n_ops++;
+ object->lookup_jif = jiffies;
+ object->state = FSCACHE_OBJECT_LOOKING_UP;
+ set_bit(FSCACHE_OBJECT_EV_REQUEUE, &object->events);
+ }
+
+ spin_unlock(&parent->lock);
+ }
+
+ spin_unlock(&object->cookie->parent->lock);
+ spin_unlock(&object->cookie->lock);
+ _leave("");
+}
+
+/*
+ * look up an object in its cache
+ * - we hold an "access lock" on the parent object, so the parent object cannot
+ * be withdrawn by either party till we've finished
+ * - an object's cookie is pinned until we clear FSCACHE_COOKIE_CREATING on the
+ * leaf-most cookies of the object and all its children
+ */
+static void fscache_lookup_object(struct fscache_object *object)
+{
+ struct fscache_cookie *cookie = object->cookie;
+ struct fscache_object *parent;
+
+ _enter("");
+
+ parent = object->parent;
+ ASSERT(parent != NULL);
+ ASSERTCMP(parent->n_ops, >, 0);
+
+ /* make sure the parent is still available */
+ ASSERTCMP(parent->state, >=, FSCACHE_OBJECT_AVAILABLE);
+
+ if (parent->state >= FSCACHE_OBJECT_DYING ||
+ test_bit(FSCACHE_IOERROR, &object->cache->flags)) {
+ _debug("unavailable");
+ set_bit(FSCACHE_OBJECT_EV_WITHDRAW, &object->events);
+ _leave("");
+ return;
+ }
+
+ _debug("LOOKUP \"%s/%s\" in \"%s\"",
+ parent->cookie->def->name, cookie->def->name,
+ object->cache->tag->name);
+
+ fscache_stat(&fscache_n_object_lookups);
+ object->cache->ops->lookup_object(object);
+
+ if (test_bit(FSCACHE_OBJECT_EV_ERROR, &object->events))
+ set_bit(FSCACHE_COOKIE_UNAVAILABLE, &cookie->flags);
+
+ _leave("");
+}
+
+/**
+ * fscache_object_lookup_negative - Note negative cookie lookup
+ * @object: Object pointing to cookie to mark
+ *
+ * Note negative lookup, permitting those waiting to read data from an already
+ * existing backing object to continue as there's no data for them to read.
+ */
+void fscache_object_lookup_negative(struct fscache_object *object)
+{
+ struct fscache_cookie *cookie = object->cookie;
+
+ _enter("{OBJ%x,%s}",
+ object->debug_id, fscache_object_states[object->state]);
+
+ if (object->state == FSCACHE_OBJECT_LOOKING_UP) {
+ fscache_stat(&fscache_n_object_lookups_negative);
+
+ /* transit here to allow write requests to begin stacking up
+ * and read requests to begin returning ENODATA */
+ object->state = FSCACHE_OBJECT_CREATING;
+
+ set_bit(FSCACHE_COOKIE_PENDING_FILL, &cookie->flags);
+ set_bit(FSCACHE_COOKIE_NO_DATA_YET, &cookie->flags);
+
+ _debug("wake up lookup %p", &cookie->flags);
+ smp_mb__before_clear_bit();
+ clear_bit(FSCACHE_COOKIE_LOOKING_UP, &cookie->flags);
+ smp_mb__after_clear_bit();
+ wake_up_bit(&cookie->flags, FSCACHE_COOKIE_LOOKING_UP);
+ clear_bit(FSCACHE_OBJECT_SYNC, &object->flags);
+ set_bit(FSCACHE_OBJECT_EV_REQUEUE, &object->events);
+ } else {
+ ASSERTCMP(object->state, ==, FSCACHE_OBJECT_CREATING);
+ }
+
+ _leave("");
+}
+
+EXPORT_SYMBOL(fscache_object_lookup_negative);
+
+/**
+ * fscache_obtained_object - Note successful object lookup or creation
+ * @object: Object pointing to cookie to mark
+ *
+ * Note successful lookup and/or creation, permitting those waiting to write
+ * data to a backing object to continue.
+ *
+ * Note that after calling this, an object's cookie may be relinquished by the
+ * netfs, and so must be accessed with object lock held.
+ */
+void fscache_obtained_object(struct fscache_object *object)
+{
+ struct fscache_cookie *cookie = object->cookie;
+
+ _enter("{OBJ%x,%s}",
+ object->debug_id, fscache_object_states[object->state]);
+
+ /* if we were still looking up, then we must have a positive lookup
+ * result, in which case there may be data available */
+ if (object->state == FSCACHE_OBJECT_LOOKING_UP) {
+ fscache_stat(&fscache_n_object_lookups_positive);
+
+ clear_bit(FSCACHE_COOKIE_NO_DATA_YET, &cookie->flags);
+
+ object->state = FSCACHE_OBJECT_AVAILABLE;
+
+ smp_mb__before_clear_bit();
+ clear_bit(FSCACHE_COOKIE_LOOKING_UP, &cookie->flags);
+ smp_mb__after_clear_bit();
+ wake_up_bit(&cookie->flags, FSCACHE_COOKIE_LOOKING_UP);
+ clear_bit(FSCACHE_OBJECT_SYNC, &object->flags);
+ set_bit(FSCACHE_OBJECT_EV_REQUEUE, &object->events);
+ } else {
+ ASSERTCMP(object->state, ==, FSCACHE_OBJECT_CREATING);
+ fscache_stat(&fscache_n_object_created);
+
+ object->state = FSCACHE_OBJECT_AVAILABLE;
+ set_bit(FSCACHE_OBJECT_EV_REQUEUE, &object->events);
+ smp_wmb();
+ }
+
+ if (test_and_clear_bit(FSCACHE_COOKIE_CREATING, &cookie->flags))
+ wake_up_bit(&cookie->flags, FSCACHE_COOKIE_CREATING);
+
+ _leave("");
+}
+
+EXPORT_SYMBOL(fscache_obtained_object);
+
+/*
+ * handle an object that has just become available
+ */
+static void fscache_object_available(struct fscache_object *object)
+{
+ _enter("{OBJ%x}", object->debug_id);
+
+ spin_lock(&object->lock);
+ if (test_and_clear_bit(FSCACHE_COOKIE_CREATING, &object->cookie->flags))
+ wake_up_bit(&object->cookie->flags, FSCACHE_COOKIE_CREATING);
+
+ spin_lock_nested(&object->parent->lock, 1);
+ object->parent->n_ops--;
+ if (object->parent->n_ops == 0)
+ fscache_raise_event(object->parent, FSCACHE_OBJECT_EV_CLEARED);
+ spin_unlock(&object->parent->lock);
+
+ if (object->n_ops > 0)
+ fscache_start_operations(object);
+ spin_unlock(&object->lock);
+
+ object->cache->ops->lookup_complete(object);
+ fscache_enqueue_dependents(object);
+
+ if (test_bit(FSCACHE_OBJECT_BOOSTED, &object->flags))
+ fscache_hist(fscache_obj_instantiate_histogram,
+ object->lookup_jif);
+ fscache_stat(&fscache_n_object_avail);
+
+ _leave("");
+}
+
+/*
+ * drop an object's attachments
+ */
+static void fscache_drop_object(struct fscache_object *object)
+{
+ struct fscache_object *parent = object->parent;
+ struct fscache_cache *cache = object->cache;
+
+ _enter("{OBJ%x,%d}", object->debug_id, object->n_children);
+
+ spin_lock(&cache->object_list_lock);
+ list_del_init(&object->cache_link);
+ spin_unlock(&cache->object_list_lock);
+
+ cache->ops->drop_object(object);
+
+ if (parent) {
+ _debug("release parent OBJ%x {%d}",
+ parent->debug_id, parent->n_children);
+
+ spin_lock(&parent->lock);
+ parent->n_children--;
+ if (parent->n_children == 0)
+ fscache_raise_event(parent, FSCACHE_OBJECT_EV_CLEARED);
+ spin_unlock(&parent->lock);
+ object->parent = NULL;
+ }
+
+ /* this just shifts the object release to the fscache thread pool */
+ object->cache->ops->put_object(object);
+}
+
+/*
+ * release or recycle an object
+ */
+static void fscache_release_object(struct fscache_object *object)
+{
+ _enter("");
+
+ fscache_drop_object(object);
+
+ _leave("");
+}
+
+/*
+ * withdraw an object from active service
+ */
+static void fscache_withdraw_object(struct fscache_object *object)
+{
+ struct fscache_cookie *cookie;
+ bool detached;
+
+ _enter("");
+
+ spin_lock(&object->lock);
+ cookie = object->cookie;
+ if (cookie) {
+ /* need to get the cookie lock before the object lock, starting
+ * from the object pointer */
+ atomic_inc(&cookie->usage);
+ spin_unlock(&object->lock);
+
+ detached = false;
+ spin_lock(&cookie->lock);
+ spin_lock(&object->lock);
+
+ if (object->cookie == cookie) {
+ hlist_del_init(&object->cookie_link);
+ object->cookie = NULL;
+ detached = true;
+ }
+ spin_unlock(&cookie->lock);
+ fscache_cookie_put(cookie);
+ if (detached)
+ fscache_cookie_put(cookie);
+ }
+
+ spin_unlock(&object->lock);
+
+ fscache_drop_object(object);
+
+ _leave("");
+}
+
+/*
+ * withdraw an object from active service at the behest of the cache
+ * - need break the links to a cached object cookie
+ * - called under two situations:
+ * (1) recycler decides to reclaim an in-use object
+ * (2) a cache is unmounted
+ * - have to take care as the cookie can be being relinquished by the netfs
+ * simultaneously
+ * - the object is pinned by the caller holding a refcount on it
+ */
+void fscache_withdrawing_object(struct fscache_cache *cache,
+ struct fscache_object *object)
+{
+ bool enqueue = false;
+
+ _enter(",OBJ%x", object->debug_id);
+
+ spin_lock(&object->lock);
+ if (object->state < FSCACHE_OBJECT_WITHDRAWING) {
+ object->state = FSCACHE_OBJECT_WITHDRAWING;
+ enqueue = true;
+ }
+ spin_unlock(&object->lock);
+
+ if (enqueue)
+ fscache_enqueue_object(object);
+
+ _leave("");
+}
diff --git a/fs/fscache/fsc-page.c b/fs/fscache/fsc-page.c
new file mode 100644
index 0000000..25ffe70
--- /dev/null
+++ b/fs/fscache/fsc-page.c
@@ -0,0 +1,866 @@
+/* Cache page management and data I/O routines
+ *
+ * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL PAGE
+#include <linux/module.h>
+#include <linux/fscache-cache.h>
+#include <linux/buffer_head.h>
+#include <linux/pagevec.h>
+#include "fsc-internal.h"
+
+/*
+ * start an op running
+ */
+static void fscache_run_op(struct fscache_object *object,
+ struct fscache_operation *op)
+{
+ object->n_in_progress++;
+ clear_bit(FSCACHE_OP_WAITING, &op->flags);
+ if (op->processor)
+ fscache_enqueue_operation(op);
+ fscache_stat(&fscache_n_op_run);
+}
+
+/*
+ * submit an exclusive operation for an object
+ * - other ops are excluded from running simultaneously with this one
+ */
+static int fscache_submit_exclusive_op(struct fscache_object *object,
+ struct fscache_operation *op)
+{
+ int ret;
+
+ spin_lock(&object->lock);
+ ASSERTCMP(object->n_ops, >=, object->n_in_progress);
+ ASSERTCMP(object->n_ops, >=, object->n_exclusive);
+
+ ret = -ENOBUFS;
+ if (fscache_object_is_active(object)) {
+ op->object = object;
+ object->n_ops++;
+ object->n_exclusive++; /* reads and writes must wait */
+
+ if (object->n_ops > 0) {
+ list_add_tail(&op->work_link, &object->pending_ops);
+ fscache_stat(&fscache_n_op_pend);
+ } else {
+ ASSERTCMP(object->n_in_progress, ==, 0);
+ fscache_run_op(object, op);
+ }
+
+ /* need to issue a new write op after this */
+ clear_bit(FSCACHE_OBJECT_PENDING_WRITE, &object->flags);
+ ret = 0;
+ } else if (object->state == FSCACHE_OBJECT_CREATING) {
+ op->object = object;
+ object->n_ops++;
+ object->n_exclusive++; /* reads and writes must wait */
+ list_add_tail(&op->work_link, &object->pending_ops);
+ fscache_stat(&fscache_n_op_pend);
+ ret = 0;
+ } else {
+ /* not allowed to submit ops in any other state */
+ BUG();
+ }
+
+ spin_unlock(&object->lock);
+ return ret;
+}
+
+/*
+ * submit an operation for an object
+ */
+static int fscache_submit_op(struct fscache_object *object,
+ struct fscache_operation *op)
+{
+ int ret;
+
+ spin_lock(&object->lock);
+ ASSERTCMP(object->n_ops, >=, object->n_in_progress);
+ ASSERTCMP(object->n_ops, >=, object->n_exclusive);
+
+ if (fscache_object_is_active(object)) {
+ op->object = object;
+ object->n_ops++;
+
+ if (object->n_exclusive > 0) {
+ ASSERTCMP(object->n_in_progress, >, 0);
+ list_add_tail(&op->work_link, &object->pending_ops);
+ fscache_stat(&fscache_n_op_pend);
+ } else {
+ ASSERT(list_empty(&object->pending_ops));
+ ASSERTCMP(object->n_exclusive, ==, 0);
+ fscache_run_op(object, op);
+ }
+ ret = 0;
+ } else if (object->state == FSCACHE_OBJECT_CREATING) {
+ op->object = object;
+ object->n_ops++;
+ list_add_tail(&op->work_link, &object->pending_ops);
+ fscache_stat(&fscache_n_op_pend);
+ ret = 0;
+ } else if (!test_bit(FSCACHE_IOERROR, &object->cache->flags)) {
+ static bool once_only;
+ if (!once_only) {
+ once_only = true;
+ kdebug("no submit [OBJ%x %s]",
+ object->debug_id,
+ fscache_object_states[object->state]);
+ dump_stack();
+ }
+ ret = -ENOBUFS;
+ } else {
+ ret = -ENOBUFS;
+ }
+
+ spin_unlock(&object->lock);
+ return ret;
+}
+
+/*
+ * queue an object for withdrawal on error, aborting all following asynchronous
+ * operations
+ */
+static void fscache_abort_object(struct fscache_object *object)
+{
+ fscache_raise_event(object, FSCACHE_OBJECT_EV_ERROR);
+}
+
+/*
+ * actually apply the changed attributes to a cache object
+ */
+static void fscache_attr_changed_op(struct fscache_operation *op)
+{
+ struct fscache_object *object = op->object;
+
+ _enter("{OBJ%x}", object->debug_id);
+
+ fscache_stat(&fscache_n_attr_changed_calls);
+
+ if (fscache_object_is_active(object) &&
+ object->cache->ops->attr_changed(object) < 0)
+ fscache_abort_object(object);
+
+ _leave("");
+}
+
+/*
+ * notification that the attributes on an object have changed
+ */
+int __fscache_attr_changed(struct fscache_cookie *cookie)
+{
+ struct fscache_operation *op;
+ struct fscache_object *object;
+
+ _enter("%p", cookie);
+
+ ASSERTCMP(cookie->def->type, !=, FSCACHE_COOKIE_TYPE_INDEX);
+
+ fscache_stat(&fscache_n_attr_changed);
+
+ op = kzalloc(sizeof(*op), GFP_KERNEL);
+ if (!op) {
+ fscache_stat(&fscache_n_attr_changed_nomem);
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+ }
+
+ op->flags |= 1 << FSCACHE_OP_EXCLUSIVE;
+ op->processor = fscache_attr_changed_op;
+
+ spin_lock(&cookie->lock);
+
+ if (hlist_empty(&cookie->backing_objects))
+ goto nobufs;
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+
+ if (fscache_submit_exclusive_op(object, op) < 0)
+ goto nobufs;
+ spin_unlock(&cookie->lock);
+ fscache_stat(&fscache_n_attr_changed_ok);
+ _leave(" = 0");
+ return 0;
+
+nobufs:
+ spin_unlock(&cookie->lock);
+ kfree(op);
+ fscache_stat(&fscache_n_attr_changed_nobufs);
+ _leave(" = %d", -ENOBUFS);
+ return -ENOBUFS;
+}
+
+EXPORT_SYMBOL(__fscache_attr_changed);
+
+/*
+ * release a retrieval op reference
+ */
+static void fscache_release_retrieval_op(struct fscache_operation *_op)
+{
+ struct fscache_retrieval *op =
+ container_of(_op, struct fscache_retrieval, op);
+
+ _enter("");
+
+ fscache_hist(fscache_retrieval_histogram, op->start_time);
+ if (op->context)
+ fscache_put_context(op->op.object->cookie, op->context);
+
+ _leave("");
+}
+
+/*
+ * allocate a retrieval op
+ */
+static struct fscache_retrieval *fscache_alloc_retrieval(
+ struct address_space *mapping,
+ fscache_rw_complete_t end_io_func,
+ void *context)
+{
+ struct fscache_retrieval *op;
+
+ /* allocate a retrieval operation and attempt to submit it */
+ op = kzalloc(sizeof(*op), GFP_NOIO);
+ if (!op) {
+ fscache_stat(&fscache_n_retrievals_nomem);
+ return NULL;
+ }
+
+ atomic_set(&op->op.usage, 1);
+ op->op.flags = (1 << FSCACHE_OP_WAITING) | (1 << FSCACHE_OP_SYNC);
+ op->op.release = fscache_release_retrieval_op;
+ op->mapping = mapping;
+ op->end_io_func = end_io_func;
+ op->context = context;
+ op->start_time = jiffies;
+ INIT_LIST_HEAD(&op->op.work_link);
+ INIT_LIST_HEAD(&op->to_do);
+ return op;
+}
+
+/*
+ * wait for a deferred lookup to complete
+ */
+static int fscache_wait_for_deferred_lookup(struct fscache_cookie *cookie)
+{
+ struct fscache_object *object;
+ unsigned long jif;
+
+ if (!test_bit(FSCACHE_COOKIE_LOOKING_UP, &cookie->flags))
+ return 0;
+
+ fscache_stat(&fscache_n_retrievals_wait);
+
+ /* tell the cookie dispatcher that this cookie is now being waited
+ * upon for lookup completion */
+ spin_lock(&cookie->lock);
+ if (!hlist_empty(&cookie->backing_objects)) {
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+ if (!test_and_set_bit(FSCACHE_OBJECT_SYNC, &object->flags))
+ fscache_boost_object(object);
+ }
+ spin_unlock(&cookie->lock);
+
+ jif = jiffies;
+ if (wait_on_bit(&cookie->flags, FSCACHE_COOKIE_LOOKING_UP,
+ fscache_wait_bit_interruptible,
+ TASK_INTERRUPTIBLE) != 0) {
+ fscache_stat(&fscache_n_retrievals_intr);
+ return -ERESTARTSYS;
+ }
+
+ ASSERT(!test_bit(FSCACHE_COOKIE_LOOKING_UP, &cookie->flags));
+
+ smp_rmb();
+ fscache_hist(fscache_retrieval_delay_histogram, jif);
+ return 0;
+}
+
+/*
+ * read a page from the cache or allocate a block in which to store it
+ * - we return:
+ * -ENOMEM - out of memory, nothing done
+ * -ERESTARTSYS - interrupted
+ * -ENOBUFS - no backing object available in which to cache the block
+ * -ENODATA - no data available in the backing object for this block
+ * 0 - dispatched a read - it'll call end_io_func() when finished
+ */
+int __fscache_read_or_alloc_page(struct fscache_cookie *cookie,
+ struct page *page,
+ fscache_rw_complete_t end_io_func,
+ void *context,
+ gfp_t gfp)
+{
+ struct fscache_retrieval *op;
+ struct fscache_object *object;
+ int ret;
+
+ _enter("%p,%p,,,", cookie, page);
+
+ fscache_stat(&fscache_n_retrievals);
+
+ if (hlist_empty(&cookie->backing_objects))
+ goto nobufs;
+
+ ASSERTCMP(cookie->def->type, !=, FSCACHE_COOKIE_TYPE_INDEX);
+ ASSERTCMP(page, !=, NULL);
+
+ if (fscache_wait_for_deferred_lookup(cookie) < 0)
+ return -ERESTARTSYS;
+
+ op = fscache_alloc_retrieval(page->mapping, end_io_func, context);
+ if (!op) {
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+ }
+
+ spin_lock(&cookie->lock);
+
+ if (hlist_empty(&cookie->backing_objects))
+ goto nobufs_unlock;
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+
+ ASSERTCMP(object->state, >, FSCACHE_COOKIE_LOOKING_UP);
+
+ if (fscache_submit_op(object, &op->op) < 0)
+ goto nobufs_unlock;
+ spin_unlock(&cookie->lock);
+
+ fscache_stat(&fscache_n_retrieval_ops);
+
+ /* pin the netfs read context in case we need to do the actual netfs
+ * read because we've encountered a cache read failure */
+ fscache_get_context(object->cookie, op->context);
+
+ if (test_bit(FSCACHE_OP_WAITING, &op->op.flags)) {
+ _debug(">>> WT");
+ fscache_stat(&fscache_n_retrieval_op_waits);
+ wait_on_bit(&op->op.flags, FSCACHE_OP_WAITING,
+ fscache_wait_bit, TASK_UNINTERRUPTIBLE);
+ _debug("<<< GO");
+ }
+
+ /* ask the cache to honour the operation */
+ if (test_bit(FSCACHE_COOKIE_NO_DATA_YET, &object->cookie->flags)) {
+ ret = object->cache->ops->allocate_page(op, page, gfp);
+ if (ret == 0)
+ ret = -ENODATA;
+ } else {
+ ret = object->cache->ops->read_or_alloc_page(op, page, gfp);
+ }
+
+ if (ret == -ENOMEM)
+ fscache_stat(&fscache_n_retrievals_nomem);
+ else if (ret == -ERESTARTSYS)
+ fscache_stat(&fscache_n_retrievals_intr);
+ else if (ret == -ENODATA)
+ fscache_stat(&fscache_n_retrievals_nodata);
+ else if (ret < 0)
+ fscache_stat(&fscache_n_retrievals_nobufs);
+ else
+ fscache_stat(&fscache_n_retrievals_ok);
+
+ fscache_put_retrieval(op);
+ _leave(" = %d", ret);
+ return ret;
+
+nobufs_unlock:
+ spin_unlock(&cookie->lock);
+ kfree(op);
+nobufs:
+ fscache_stat(&fscache_n_retrievals_nobufs);
+ _leave(" = -ENOBUFS");
+ return -ENOBUFS;
+}
+
+EXPORT_SYMBOL(__fscache_read_or_alloc_page);
+
+/*
+ * read a list of page from the cache or allocate a block in which to store
+ * them
+ * - we return:
+ * -ENOMEM - out of memory, some pages may be being read
+ * -ERESTARTSYS - interrupted, some pages may be being read
+ * -ENOBUFS - no backing object or space available in which to cache any
+ * pages not being read
+ * -ENODATA - no data available in the backing object for some or all of
+ * the pages
+ * 0 - dispatched a read on all pages
+ *
+ * end_io_func() will be called for each page read from the cache as it is
+ * finishes being read
+ *
+ * any pages for which a read is dispatched will be removed from pages and
+ * nr_pages
+ */
+int __fscache_read_or_alloc_pages(struct fscache_cookie *cookie,
+ struct address_space *mapping,
+ struct list_head *pages,
+ unsigned *nr_pages,
+ fscache_rw_complete_t end_io_func,
+ void *context,
+ gfp_t gfp)
+{
+ fscache_pages_retrieval_func_t func;
+ struct fscache_retrieval *op;
+ struct fscache_object *object;
+ int ret;
+
+ _enter("%p,,%d,,,", cookie, *nr_pages);
+
+ fscache_stat(&fscache_n_retrievals);
+
+ if (hlist_empty(&cookie->backing_objects))
+ goto nobufs;
+
+ ASSERTCMP(cookie->def->type, !=, FSCACHE_COOKIE_TYPE_INDEX);
+ ASSERTCMP(*nr_pages, >, 0);
+ ASSERT(!list_empty(pages));
+
+ if (fscache_wait_for_deferred_lookup(cookie) < 0)
+ return -ERESTARTSYS;
+
+ op = fscache_alloc_retrieval(mapping, end_io_func, context);
+ if (!op)
+ return -ENOMEM;
+
+ spin_lock(&cookie->lock);
+
+ if (hlist_empty(&cookie->backing_objects))
+ goto nobufs_unlock;
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+
+ if (fscache_submit_op(object, &op->op) < 0)
+ goto nobufs_unlock;
+ spin_unlock(&cookie->lock);
+
+ fscache_stat(&fscache_n_retrieval_ops);
+
+ /* pin the netfs read context in case we need to do the actual netfs
+ * read because we've encountered a cache read failure */
+ fscache_get_context(object->cookie, op->context);
+
+ if (test_bit(FSCACHE_OP_WAITING, &op->op.flags)) {
+ _debug(">>> WT");
+ fscache_stat(&fscache_n_retrieval_op_waits);
+ wait_on_bit(&op->op.flags, FSCACHE_OP_WAITING,
+ fscache_wait_bit, TASK_UNINTERRUPTIBLE);
+ _debug("<<< GO");
+ }
+
+ /* ask the cache to honour the operation */
+ if (test_bit(FSCACHE_COOKIE_NO_DATA_YET, &object->cookie->flags))
+ func = object->cache->ops->allocate_pages;
+ else
+ func = object->cache->ops->read_or_alloc_pages;
+ ret = func(op, pages, nr_pages, gfp);
+
+ if (ret == -ENOMEM)
+ fscache_stat(&fscache_n_retrievals_nomem);
+ else if (ret == -ERESTARTSYS)
+ fscache_stat(&fscache_n_retrievals_intr);
+ else if (ret == -ENODATA)
+ fscache_stat(&fscache_n_retrievals_nodata);
+ else if (ret < 0)
+ fscache_stat(&fscache_n_retrievals_nobufs);
+ else
+ fscache_stat(&fscache_n_retrievals_ok);
+
+ fscache_put_retrieval(op);
+ _leave(" = %d", ret);
+ return ret;
+
+nobufs_unlock:
+ spin_unlock(&cookie->lock);
+ kfree(op);
+nobufs:
+ fscache_stat(&fscache_n_retrievals_nobufs);
+ _leave(" = -ENOBUFS");
+ return -ENOBUFS;
+}
+
+EXPORT_SYMBOL(__fscache_read_or_alloc_pages);
+
+/*
+ * allocate a block in the cache on which to store a page
+ * - we return:
+ * -ENOMEM - out of memory, nothing done
+ * -ERESTARTSYS - interrupted
+ * -ENOBUFS - no backing object available in which to cache the block
+ * 0 - block allocated
+ */
+int __fscache_alloc_page(struct fscache_cookie *cookie,
+ struct page *page,
+ gfp_t gfp)
+{
+ struct fscache_retrieval *op;
+ struct fscache_object *object;
+ int ret;
+
+ _enter("%p,%p,,,", cookie, page);
+
+ fscache_stat(&fscache_n_allocs);
+
+ if (hlist_empty(&cookie->backing_objects))
+ goto nobufs;
+
+ ASSERTCMP(cookie->def->type, !=, FSCACHE_COOKIE_TYPE_INDEX);
+ ASSERTCMP(page, !=, NULL);
+
+ if (fscache_wait_for_deferred_lookup(cookie) < 0)
+ return -ERESTARTSYS;
+
+ op = fscache_alloc_retrieval(page->mapping, NULL, NULL);
+ if (!op)
+ return -ENOMEM;
+
+ spin_lock(&cookie->lock);
+
+ if (hlist_empty(&cookie->backing_objects))
+ goto nobufs_unlock;
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+
+ if (fscache_submit_op(object, &op->op) < 0)
+ goto nobufs_unlock;
+ spin_unlock(&cookie->lock);
+
+ fscache_stat(&fscache_n_alloc_ops);
+
+ if (test_bit(FSCACHE_OP_WAITING, &op->op.flags)) {
+ _debug(">>> WT");
+ fscache_stat(&fscache_n_alloc_op_waits);
+ wait_on_bit(&op->op.flags, FSCACHE_OP_WAITING,
+ fscache_wait_bit, TASK_UNINTERRUPTIBLE);
+ _debug("<<< GO");
+ }
+
+ /* ask the cache to honour the operation */
+ ret = object->cache->ops->allocate_page(op, page, gfp);
+
+ if (ret < 0)
+ fscache_stat(&fscache_n_allocs_nobufs);
+ else
+ fscache_stat(&fscache_n_allocs_ok);
+
+ fscache_put_retrieval(op);
+ _leave(" = %d", ret);
+ return ret;
+
+nobufs_unlock:
+ spin_unlock(&cookie->lock);
+ kfree(op);
+nobufs:
+ fscache_stat(&fscache_n_allocs_nobufs);
+ _leave(" = -ENOBUFS");
+ return -ENOBUFS;
+}
+
+EXPORT_SYMBOL(__fscache_alloc_page);
+
+/*
+ * store a page in the cache in the background
+ */
+static void fscache_write_op(struct fscache_operation *_op)
+{
+ struct fscache_storage *op =
+ container_of(_op, struct fscache_storage, op);
+ struct fscache_object *object = op->op.object;
+ struct page *page;
+ unsigned n;
+ void *results[1];
+ int ret;
+
+ _enter("");
+
+ if (!fscache_object_is_active(object)) {
+ fscache_put_operation(&op->op);
+ _leave("");
+ return;
+ }
+
+ fscache_stat(&fscache_n_store_calls);
+
+ /* find a page to store */
+ spin_lock(&object->lock);
+
+ page = NULL;
+ n = radix_tree_gang_lookup(&object->stores, results, 0, 1);
+ if (n == 1) {
+ page = results[0];
+ _debug("gang %d [%lx]", n, page->index);
+ if (page->index <= op->store_limit)
+ radix_tree_delete(&object->stores, page->index);
+ else
+ goto superseded;
+ } else {
+ goto superseded;
+ }
+
+ spin_unlock(&object->lock);
+
+ if (page) {
+ ret = object->cache->ops->write_page(op, page);
+ end_page_fscache_write(page);
+ page_cache_release(page);
+ if (ret < 0) {
+ fscache_abort_object(object);
+ fscache_put_operation(&op->op);
+ } else {
+ fscache_enqueue_operation(&op->op);
+ }
+ }
+
+ _leave("");
+ return;
+
+superseded:
+ /* this writer is going away and there aren't any more things to
+ * write */
+ _debug("cease");
+ clear_bit(FSCACHE_OBJECT_PENDING_WRITE, &object->flags);
+ spin_unlock(&object->lock);
+ _leave("");
+}
+
+/*
+ * request a page be stored in the cache
+ * - returns:
+ * -ENOMEM - out of memory, nothing done
+ * -ENOBUFS - no backing object available in which to cache the page
+ * 0 - dispatched a write - it'll call end_io_func() when finished
+ *
+ * if the cookie still has a backing object at this point, that object can be
+ * in one of a few states with respect to storage processing:
+ *
+ * (1) negative lookup, object not yet created (FSCACHE_COOKIE_CREATING is
+ * set)
+ *
+ * (a) no writes yet (set FSCACHE_COOKIE_PENDING_FILL and queue deferred
+ * fill op)
+ *
+ * (b) writes deferred till post-creation (mark page for writing and
+ * return immediately)
+ *
+ * (2) negative lookup, object created, initial fill being made from netfs
+ * (FSCACHE_COOKIE_INITIAL_FILL is set)
+ *
+ * (a) fill point not yet reached this page (mark page for writing and
+ * return)
+ *
+ * (b) fill point passed this page (queue op to store this page)
+ *
+ * (3) object extant (queue op to store this page)
+ *
+ * any other state is invalid
+ */
+int __fscache_write_page(struct fscache_cookie *cookie,
+ struct page *page,
+ gfp_t gfp)
+{
+ struct fscache_storage *op;
+ struct fscache_object *object;
+ int ret;
+
+ _enter("%p,%x,", cookie, (u32) page->flags);
+
+ ASSERTCMP(cookie->def->type, !=, FSCACHE_COOKIE_TYPE_INDEX);
+ ASSERT(PageFsCache(page));
+
+ fscache_stat(&fscache_n_stores);
+
+ op = kzalloc(sizeof(*op), GFP_NOIO);
+ if (!op) {
+ fscache_stat(&fscache_n_stores_oom);
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+ }
+
+ ret = radix_tree_preload(gfp & ~__GFP_HIGHMEM);
+ if (ret < 0) {
+ kfree(op);
+ fscache_stat(&fscache_n_stores_oom);
+ _leave(" = %d", ret);
+ return ret;
+ }
+
+ ret = -ENOBUFS;
+ spin_lock(&cookie->lock);
+
+ if (hlist_empty(&cookie->backing_objects))
+ goto nobufs;
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+ if (test_bit(FSCACHE_IOERROR, &object->cache->flags))
+ goto nobufs;
+
+ /* add the page to the pending-storage radix tree on the backing
+ * object */
+ spin_lock(&object->lock);
+
+ _debug("store limit %llx", (unsigned long long) object->store_limit);
+
+ ret = radix_tree_insert(&object->stores, page->index, page);
+ if (ret < 0) {
+ if (ret == -EEXIST)
+ goto already_queued;
+ _debug("insert failed %d", ret);
+ ret = -ENOBUFS;
+ goto nobufs_unlock;
+ }
+
+ page_cache_get(page);
+ if (TestSetPageFsCacheWrite(page))
+ BUG();
+
+ /* we only want one writer at a time, but we do need to queue new
+ * writers after exclusive ops */
+ if (test_and_set_bit(FSCACHE_OBJECT_PENDING_WRITE, &object->flags))
+ goto already_pending;
+
+ spin_unlock(&object->lock);
+
+ op->op.processor = fscache_write_op;
+ op->store_limit = object->store_limit;
+ INIT_LIST_HEAD(&op->op.work_link);
+
+ if (fscache_submit_op(object, &op->op) < 0) {
+ radix_tree_delete(&object->stores, page->index);
+ end_page_fscache_write(page);
+ page_cache_release(page);
+ ret = -ENOBUFS;
+ goto nobufs;
+ }
+
+ spin_unlock(&cookie->lock);
+ radix_tree_preload_end();
+ fscache_stat(&fscache_n_store_ops);
+ fscache_stat(&fscache_n_stores_ok);
+ _leave(" = 0");
+ return 0;
+
+already_queued:
+ fscache_stat(&fscache_n_stores_again);
+already_pending:
+ spin_unlock(&object->lock);
+ spin_unlock(&cookie->lock);
+ radix_tree_preload_end();
+ kfree(op);
+ fscache_stat(&fscache_n_stores_ok);
+ _leave(" = 0");
+ return 0;
+
+nobufs_unlock:
+ spin_unlock(&object->lock);
+nobufs:
+ spin_unlock(&cookie->lock);
+ radix_tree_preload_end();
+ kfree(op);
+ fscache_stat(&fscache_n_stores_nobufs);
+ _leave(" = -ENOBUFS");
+ return -ENOBUFS;
+}
+
+EXPORT_SYMBOL(__fscache_write_page);
+
+/*
+ * remove a page from the cache
+ */
+void __fscache_uncache_page(struct fscache_cookie *cookie, struct page *page)
+{
+ struct fscache_object *object;
+
+ _enter(",%p", page);
+
+ ASSERTCMP(cookie->def->type, !=, FSCACHE_COOKIE_TYPE_INDEX);
+ ASSERTCMP(page, !=, NULL);
+
+ fscache_stat(&fscache_n_uncaches);
+
+ /* cache withdrawal may beat us to it */
+ if (!PageFsCache(page))
+ goto done;
+
+ /* get the object */
+ spin_lock(&cookie->lock);
+
+ if (hlist_empty(&cookie->backing_objects)) {
+ ClearPageFsCache(page);
+ goto done_unlock;
+ }
+
+ object = hlist_entry(cookie->backing_objects.first,
+ struct fscache_object, cookie_link);
+
+ /* there might now be stuff on disk we could read */
+ clear_bit(FSCACHE_COOKIE_NO_DATA_YET, &cookie->flags);
+
+ /* only invoke the cache backend if we managed to mark the page
+ * uncached here; this deals with synchronisation vs withdrawal */
+ if (TestClearPageFsCache(page) &&
+ object->cache->ops->uncache_page) {
+ /* the cache backend releases the cookie lock */
+ object->cache->ops->uncache_page(object, page);
+ goto done;
+ }
+
+done_unlock:
+ spin_unlock(&cookie->lock);
+done:
+ _leave("");
+}
+
+EXPORT_SYMBOL(__fscache_uncache_page);
+
+/**
+ * fscache_mark_pages_cached - Mark pages as being cached
+ * @op: The retrieval op pages are being marked for
+ * @pagevec: The pages to be marked
+ *
+ * Mark a bunch of netfs pages as being cached. After this is called,
+ * the netfs must call fscache_uncache_page() to remove the mark.
+ */
+void fscache_mark_pages_cached(struct fscache_retrieval *op,
+ struct pagevec *pagevec)
+{
+ struct fscache_cookie *cookie = op->op.object->cookie;
+ unsigned long loop;
+
+#ifdef CONFIG_FSCACHE_STATS
+ atomic_add(pagevec->nr, &fscache_n_marks);
+#endif
+
+ for (loop = 0; loop < pagevec->nr; loop++) {
+ struct page *page = pagevec->pages[loop];
+
+ _debug("- mark %p{%lx}", page, page->index);
+ if (TestSetPageFsCache(page)) {
+ static bool once_only = false;
+ if (!once_only) {
+ once_only = true;
+ printk(KERN_WARNING "FS-Cache:"
+ " Cookie type %s marked page %lx"
+ " multiple times\n",
+ cookie->def->name, page->index);
+ }
+ }
+ }
+
+ if (cookie->def->mark_pages_cached)
+ cookie->def->mark_pages_cached(cookie->netfs_data,
+ op->mapping, pagevec);
+ pagevec_reinit(pagevec);
+}
+
+EXPORT_SYMBOL(fscache_mark_pages_cached);
diff --git a/fs/fscache/fsc-proc.c b/fs/fscache/fsc-proc.c
new file mode 100644
index 0000000..1ac9d5d
--- /dev/null
+++ b/fs/fscache/fsc-proc.c
@@ -0,0 +1,399 @@
+/* FS-Cache statistics viewing interface
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL THREAD
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include "fsc-internal.h"
+
+struct fscache_proc {
+ unsigned nlines;
+ const struct seq_operations *ops;
+};
+
+struct proc_dir_entry *proc_fscache;
+
+EXPORT_SYMBOL(proc_fscache);
+
+static int fscache_proc_open(struct inode *inode, struct file *file);
+static void *fscache_proc_start(struct seq_file *m, loff_t *pos);
+static void fscache_proc_stop(struct seq_file *m, void *v);
+static void *fscache_proc_next(struct seq_file *m, void *v, loff_t *pos);
+
+static const struct file_operations fscache_proc_fops = {
+ .open = fscache_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+#ifdef CONFIG_FSCACHE_STATS
+static int fscache_stats_show(struct seq_file *m, void *v);
+static int fscache_pool_show(struct seq_file *m, void *v);
+
+static const struct seq_operations fscache_stats_ops = {
+ .start = fscache_proc_start,
+ .stop = fscache_proc_stop,
+ .next = fscache_proc_next,
+ .show = fscache_stats_show,
+};
+
+static const struct fscache_proc fscache_stats = {
+ .nlines = 16,
+ .ops = &fscache_stats_ops,
+};
+
+static const struct seq_operations fscache_pool_ops = {
+ .start = fscache_proc_start,
+ .stop = fscache_proc_stop,
+ .next = fscache_proc_next,
+ .show = fscache_pool_show,
+};
+
+static const struct fscache_proc fscache_pool = {
+ .nlines = FSCACHE_MAX_THREADS + 2,
+ .ops = &fscache_pool_ops,
+};
+#endif
+
+#ifdef CONFIG_FSCACHE_HISTOGRAM
+static int fscache_histogram_show(struct seq_file *m, void *v);
+
+static const struct seq_operations fscache_histogram_ops = {
+ .start = fscache_proc_start,
+ .stop = fscache_proc_stop,
+ .next = fscache_proc_next,
+ .show = fscache_histogram_show,
+};
+
+static const struct fscache_proc fscache_histogram = {
+ .nlines = HZ + 1,
+ .ops = &fscache_histogram_ops,
+};
+#endif
+
+#define FSC_DESC(SELECT, N) ((void *) (unsigned long) (((SELECT) << 16) | (N)))
+
+/*
+ * initialise the /proc/fs/fscache/ directory
+ */
+int __init fscache_proc_init(void)
+{
+ struct proc_dir_entry *p;
+
+ _enter("");
+
+ proc_fscache = proc_mkdir("fs/fscache", NULL);
+ if (!proc_fscache)
+ goto error_dir;
+ proc_fscache->owner = THIS_MODULE;
+
+#ifdef CONFIG_FSCACHE_STATS
+ p = create_proc_entry("stats", 0, proc_fscache);
+ if (!p)
+ goto error_stats;
+ p->proc_fops = &fscache_proc_fops;
+ p->owner = THIS_MODULE;
+ p->data = (void *) &fscache_stats;
+
+ p = create_proc_entry("pool", 0, proc_fscache);
+ if (!p)
+ goto error_pool;
+ p->proc_fops = &fscache_proc_fops;
+ p->owner = THIS_MODULE;
+ p->data = (void *) &fscache_pool;
+#endif
+
+#ifdef CONFIG_FSCACHE_HISTOGRAM
+ p = create_proc_entry("histogram", 0, proc_fscache);
+ if (!p)
+ goto error_histogram;
+ p->proc_fops = &fscache_proc_fops;
+ p->owner = THIS_MODULE;
+ p->data = (void *) &fscache_histogram;
+#endif
+
+ _leave(" = 0");
+ return 0;
+
+#ifdef CONFIG_FSCACHE_HISTOGRAM
+error_histogram:
+#endif
+#ifdef CONFIG_FSCACHE_STATS
+ remove_proc_entry("pool", proc_fscache);
+error_pool:
+ remove_proc_entry("stats", proc_fscache);
+error_stats:
+#endif
+ remove_proc_entry("fs/fscache", NULL);
+error_dir:
+ _leave(" = -ENOMEM");
+ return -ENOMEM;
+}
+
+/*
+ * clean up the /proc/fs/fscache/ directory
+ */
+void fscache_proc_cleanup(void)
+{
+#ifdef CONFIG_FSCACHE_HISTOGRAM
+ remove_proc_entry("histogram", proc_fscache);
+#endif
+#ifdef CONFIG_FSCACHE_STATS
+ remove_proc_entry("pool", proc_fscache);
+ remove_proc_entry("stats", proc_fscache);
+#endif
+ remove_proc_entry("fs/fscache", NULL);
+}
+
+/*
+ * open "/proc/fs/fscache/XXX" which provide statistics summaries
+ */
+static int fscache_proc_open(struct inode *inode, struct file *file)
+{
+ const struct fscache_proc *proc = PDE(inode)->data;
+ struct seq_file *m;
+ int ret;
+
+ ret = seq_open(file, proc->ops);
+ if (ret == 0) {
+ m = file->private_data;
+ m->private = (void *) proc;
+ }
+ return ret;
+}
+
+/*
+ * set up the iterator to start reading from the first line
+ */
+static void *fscache_proc_start(struct seq_file *m, loff_t *_pos)
+{
+ if (*_pos == 0)
+ *_pos = 1;
+ return (void *)(unsigned long) *_pos;
+}
+
+/*
+ * move to the next line
+ */
+static void *fscache_proc_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ const struct fscache_proc *proc = m->private;
+
+ (*pos)++;
+ return *pos > proc->nlines ? NULL : (void *)(unsigned long) *pos;
+}
+
+/*
+ * clean up after reading
+ */
+static void fscache_proc_stop(struct seq_file *m, void *v)
+{
+}
+
+#ifdef CONFIG_FSCACHE_STATS
+/*
+ * display the general statistics
+ */
+static int fscache_stats_show(struct seq_file *m, void *v)
+{
+ unsigned long line = (unsigned long) v;
+
+ switch (line) {
+ case 1:
+ seq_puts(m, "FS-Cache statistics\n");
+ break;
+
+ case 2:
+ seq_printf(m, "Cookies: idx=%u dat=%u spc=%u\n",
+ atomic_read(&fscache_n_cookie_index),
+ atomic_read(&fscache_n_cookie_data),
+ atomic_read(&fscache_n_cookie_special));
+ break;
+
+ case 3:
+ seq_printf(m, "Objects: alc=%u nal=%u avl=%u\n",
+ atomic_read(&fscache_n_object_alloc),
+ atomic_read(&fscache_n_object_no_alloc),
+ atomic_read(&fscache_n_object_avail));
+ break;
+
+ case 4:
+ seq_printf(m, "Pages : mrk=%u unc=%u\n",
+ atomic_read(&fscache_n_marks),
+ atomic_read(&fscache_n_uncaches));
+ break;
+
+ case 5:
+ seq_printf(m, "Acquire: n=%u nul=%u noc=%u ok=%u nbf=%u"
+ " oom=%u\n",
+ atomic_read(&fscache_n_acquires),
+ atomic_read(&fscache_n_acquires_null),
+ atomic_read(&fscache_n_acquires_no_cache),
+ atomic_read(&fscache_n_acquires_ok),
+ atomic_read(&fscache_n_acquires_nobufs),
+ atomic_read(&fscache_n_acquires_oom));
+ break;
+
+ case 6:
+ seq_printf(m, "Lookups: n=%u neg=%u pos=%u crt=%u bst=%u\n",
+ atomic_read(&fscache_n_object_lookups),
+ atomic_read(&fscache_n_object_lookups_negative),
+ atomic_read(&fscache_n_object_lookups_positive),
+ atomic_read(&fscache_n_object_created),
+ atomic_read(&fscache_n_object_boosted));
+ break;
+
+ case 7:
+ seq_printf(m, "Updates: n=%u nul=%u run=%u\n",
+ atomic_read(&fscache_n_updates),
+ atomic_read(&fscache_n_updates_null),
+ atomic_read(&fscache_n_updates_run));
+ break;
+
+ case 8:
+ seq_printf(m, "Relinqs: n=%u nul=%u wcr=%u\n",
+ atomic_read(&fscache_n_relinquishes),
+ atomic_read(&fscache_n_relinquishes_null),
+ atomic_read(&fscache_n_relinquishes_waitcrt));
+ break;
+
+ case 9:
+ seq_printf(m, "AttrChg: n=%u ok=%u nbf=%u oom=%u run=%u\n",
+ atomic_read(&fscache_n_attr_changed),
+ atomic_read(&fscache_n_attr_changed_ok),
+ atomic_read(&fscache_n_attr_changed_nobufs),
+ atomic_read(&fscache_n_attr_changed_nomem),
+ atomic_read(&fscache_n_attr_changed_calls));
+ break;
+
+ case 10:
+ seq_printf(m, "Allocs : n=%u ok=%u wt=%u nbf=%u\n",
+ atomic_read(&fscache_n_allocs),
+ atomic_read(&fscache_n_allocs_ok),
+ atomic_read(&fscache_n_allocs_wait),
+ atomic_read(&fscache_n_allocs_nobufs));
+ break;
+ case 11:
+ seq_printf(m, "Allocs : ops=%u owt=%u\n",
+ atomic_read(&fscache_n_alloc_ops),
+ atomic_read(&fscache_n_alloc_op_waits));
+ break;
+
+ case 12:
+ seq_printf(m, "Retrvls: n=%u ok=%u wt=%u nod=%u nbf=%u"
+ " int=%u oom=%u\n",
+ atomic_read(&fscache_n_retrievals),
+ atomic_read(&fscache_n_retrievals_ok),
+ atomic_read(&fscache_n_retrievals_wait),
+ atomic_read(&fscache_n_retrievals_nodata),
+ atomic_read(&fscache_n_retrievals_nobufs),
+ atomic_read(&fscache_n_retrievals_intr),
+ atomic_read(&fscache_n_retrievals_nomem));
+ break;
+ case 13:
+ seq_printf(m, "Retrvls: ops=%u owt=%u\n",
+ atomic_read(&fscache_n_retrieval_ops),
+ atomic_read(&fscache_n_retrieval_op_waits));
+ break;
+
+ case 14:
+ seq_printf(m, "Stores : n=%u ok=%u agn=%u nbf=%u oom=%u\n",
+ atomic_read(&fscache_n_stores),
+ atomic_read(&fscache_n_stores_ok),
+ atomic_read(&fscache_n_stores_again),
+ atomic_read(&fscache_n_stores_nobufs),
+ atomic_read(&fscache_n_stores_oom));
+ break;
+ case 15:
+ seq_printf(m, "Stores : ops=%u run=%u\n",
+ atomic_read(&fscache_n_store_ops),
+ atomic_read(&fscache_n_store_calls));
+ break;
+
+ case 16:
+ seq_printf(m, "Ops : pend=%u run=%u enq=%u req=%u rel=%u\n",
+ atomic_read(&fscache_n_op_pend),
+ atomic_read(&fscache_n_op_run),
+ atomic_read(&fscache_n_op_enqueue),
+ atomic_read(&fscache_n_op_requeue),
+ atomic_read(&fscache_n_op_release));
+ break;
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+/*
+ * display the per-pool-thread statistics
+ */
+static int fscache_pool_show(struct seq_file *m, void *v)
+{
+ unsigned line = (unsigned long) v;
+ unsigned x, y;
+
+ switch (line) {
+ case 1:
+ seq_puts(m, "THREAD OPERS RUN OBJS RUN\n");
+ return 0;
+ case 2:
+ seq_puts(m, "======= ========= =========\n");
+ return 0;
+ case 3 ... FSCACHE_MAX_THREADS + 2:
+ x = atomic_read(&fscache_n_ops_processed[line - 3]);
+ y = atomic_read(&fscache_n_objs_processed[line - 3]);
+ if (x != 0 || y != 0)
+ seq_printf(m, "kfsc%02ud %9u %9u\n", line - 3, x, y);
+ default:
+ return 0;
+ }
+}
+#endif
+
+#ifdef CONFIG_FSCACHE_HISTOGRAM
+/*
+ * display the time-taken histogram
+ */
+static int fscache_histogram_show(struct seq_file *m, void *v)
+{
+ unsigned long index;
+ unsigned n[5], t;
+
+ switch ((unsigned long) v) {
+ case 1:
+ seq_puts(m, "JIFS SECS OBJ INST OP RUNS OBJ RUNS "
+ " RETRV DLY RETRIEVLS\n");
+ return 0;
+ case 2:
+ seq_puts(m, "===== ===== ========= ========= ========="
+ " ========= =========\n");
+ return 0;
+ default:
+ index = (unsigned long) v - 3;
+ n[0] = atomic_read(&fscache_obj_instantiate_histogram[index]);
+ n[1] = atomic_read(&fscache_ops_histogram[index]);
+ n[2] = atomic_read(&fscache_objs_histogram[index]);
+ n[3] = atomic_read(&fscache_retrieval_delay_histogram[index]);
+ n[4] = atomic_read(&fscache_retrieval_histogram[index]);
+ if (!(n[0] | n[1] | n[2] | n[3] | n[4]))
+ return 0;
+
+ t = (index * 1000) / HZ;
+
+ seq_printf(m, "%4lu 0.%03u %9u %9u %9u %9u %9u\n",
+ index, t, n[0], n[1], n[2], n[3], n[4]);
+ return 0;
+ }
+}
+#endif
diff --git a/fs/fscache/fsc-stats.c b/fs/fscache/fsc-stats.c
new file mode 100644
index 0000000..15adbda
--- /dev/null
+++ b/fs/fscache/fsc-stats.c
@@ -0,0 +1,103 @@
+/* FS-Cache statistics
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL THREAD
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include "fsc-internal.h"
+
+/*
+ * operation counters
+ */
+#ifdef CONFIG_FSCACHE_STATS
+atomic_t fscache_n_op_pend;
+atomic_t fscache_n_op_run;
+atomic_t fscache_n_op_enqueue;
+atomic_t fscache_n_op_requeue;
+atomic_t fscache_n_op_release;
+
+atomic_t fscache_n_attr_changed;
+atomic_t fscache_n_attr_changed_ok;
+atomic_t fscache_n_attr_changed_nobufs;
+atomic_t fscache_n_attr_changed_nomem;
+atomic_t fscache_n_attr_changed_calls;
+
+atomic_t fscache_n_allocs;
+atomic_t fscache_n_allocs_ok;
+atomic_t fscache_n_allocs_wait;
+atomic_t fscache_n_allocs_nobufs;
+atomic_t fscache_n_alloc_ops;
+atomic_t fscache_n_alloc_op_waits;
+
+atomic_t fscache_n_retrievals;
+atomic_t fscache_n_retrievals_ok;
+atomic_t fscache_n_retrievals_wait;
+atomic_t fscache_n_retrievals_nodata;
+atomic_t fscache_n_retrievals_nobufs;
+atomic_t fscache_n_retrievals_intr;
+atomic_t fscache_n_retrievals_nomem;
+atomic_t fscache_n_retrieval_ops;
+atomic_t fscache_n_retrieval_op_waits;
+
+atomic_t fscache_n_stores;
+atomic_t fscache_n_stores_ok;
+atomic_t fscache_n_stores_again;
+atomic_t fscache_n_stores_nobufs;
+atomic_t fscache_n_stores_oom;
+atomic_t fscache_n_store_ops;
+atomic_t fscache_n_store_calls;
+
+atomic_t fscache_n_marks;
+atomic_t fscache_n_uncaches;
+
+atomic_t fscache_n_acquires;
+atomic_t fscache_n_acquires_null;
+atomic_t fscache_n_acquires_no_cache;
+atomic_t fscache_n_acquires_ok;
+atomic_t fscache_n_acquires_nobufs;
+atomic_t fscache_n_acquires_oom;
+
+atomic_t fscache_n_updates;
+atomic_t fscache_n_updates_null;
+atomic_t fscache_n_updates_run;
+
+atomic_t fscache_n_relinquishes;
+atomic_t fscache_n_relinquishes_null;
+atomic_t fscache_n_relinquishes_waitcrt;
+
+atomic_t fscache_n_cookie_index;
+atomic_t fscache_n_cookie_data;
+atomic_t fscache_n_cookie_special;
+
+atomic_t fscache_n_object_alloc;
+atomic_t fscache_n_object_no_alloc;
+atomic_t fscache_n_object_lookups;
+atomic_t fscache_n_object_lookups_negative;
+atomic_t fscache_n_object_lookups_positive;
+atomic_t fscache_n_object_created;
+atomic_t fscache_n_object_avail;
+atomic_t fscache_n_object_boosted;
+
+/*
+ * the number of operations and objects processed by each thread in the pool
+ */
+atomic_t fscache_n_ops_processed[FSCACHE_MAX_THREADS];
+atomic_t fscache_n_objs_processed[FSCACHE_MAX_THREADS];
+#endif
+
+#ifdef CONFIG_FSCACHE_HISTOGRAM
+atomic_t fscache_obj_instantiate_histogram[HZ];
+atomic_t fscache_objs_histogram[HZ];
+atomic_t fscache_ops_histogram[HZ];
+atomic_t fscache_retrieval_delay_histogram[HZ];
+atomic_t fscache_retrieval_histogram[HZ];
+#endif
diff --git a/fs/fscache/fsc-threads.c b/fs/fscache/fsc-threads.c
new file mode 100644
index 0000000..ff20598
--- /dev/null
+++ b/fs/fscache/fsc-threads.c
@@ -0,0 +1,590 @@
+/* FS-Cache worker thread pool manager
+ *
+ * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL THREAD
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include "fsc-internal.h"
+
+static LIST_HEAD(fscache_object_fifo);
+static LIST_HEAD(fscache_sync_object_fifo);
+static LIST_HEAD(fscache_op_fifo);
+static LIST_HEAD(fscache_sync_op_fifo);
+
+static struct task_struct *fscache_threads[FSCACHE_MAX_THREADS];
+
+static unsigned fscache_n_threads = 21;
+module_param_named(n_threads, fscache_n_threads, uint, S_IWUSR | S_IRUGO);
+MODULE_PARM_DESC(fscache_n_threads, "FS-Cache thread pool size");
+
+static DEFINE_SPINLOCK(fscache_object_lock);
+static DEFINE_SPINLOCK(fscache_operation_lock);
+static DEFINE_MUTEX(fscache_thread_mutex);
+static DECLARE_WAIT_QUEUE_HEAD(fscache_fast_threads);
+static DECLARE_WAIT_QUEUE_HEAD(fscache_slow_threads);
+
+/**
+ * fscache_enqueue_operation - Enqueue an operation for processing
+ * @op: The operation to enqueue
+ *
+ * Enqueue an operation for processing by the FS-Cache thread pool.
+ */
+void fscache_enqueue_operation(struct fscache_operation *op)
+{
+ unsigned long flags;
+ bool added = false;
+
+ _enter("{OBJ%x}", op->object->debug_id);
+
+ ASSERT(op->processor != NULL);
+ ASSERTCMP(op->object->state, >=, FSCACHE_OBJECT_AVAILABLE);
+
+ spin_lock_irqsave(&fscache_operation_lock, flags);
+
+ if (list_empty(&op->work_link) &&
+ !test_bit(FSCACHE_OP_REQUEUE, &op->flags)) {
+ if (!test_bit(FSCACHE_OP_LOCK, &op->flags)) {
+ atomic_inc(&op->usage);
+ if (test_bit(FSCACHE_OP_SYNC, &op->flags))
+ list_add_tail(&op->work_link,
+ &fscache_sync_op_fifo);
+ else
+ list_add_tail(&op->work_link,
+ &fscache_op_fifo);
+ added = true;
+ fscache_stat(&fscache_n_op_enqueue);
+ } else {
+ set_bit(FSCACHE_OP_REQUEUE, &op->flags);
+ fscache_stat(&fscache_n_op_requeue);
+ }
+ }
+
+ spin_unlock_irqrestore(&fscache_operation_lock, flags);
+ if (added)
+ wake_up(&fscache_fast_threads);
+}
+
+EXPORT_SYMBOL(fscache_enqueue_operation);
+
+/*
+ * jump start the operation processing on an object
+ * - caller must hold object->lock
+ */
+void fscache_start_operations(struct fscache_object *object)
+{
+ struct fscache_operation *op;
+ bool stop = false;
+
+ while (!list_empty(&object->pending_ops) && !stop) {
+ op = list_entry(object->pending_ops.next,
+ struct fscache_operation, work_link);
+
+ if (test_bit(FSCACHE_OP_EXCLUSIVE, &op->flags)) {
+ if (object->n_in_progress > 0)
+ break;
+ stop = true;
+ }
+ list_del_init(&op->work_link);
+ object->n_in_progress++;
+
+ if (test_and_clear_bit(FSCACHE_OP_WAITING, &op->flags))
+ wake_up_bit(&op->flags, FSCACHE_OP_WAITING);
+ if (op->processor)
+ fscache_enqueue_operation(op);
+ }
+
+ ASSERTCMP(object->n_in_progress, <=, object->n_ops);
+
+ _debug("woke %d ops on OBJ%x",
+ object->n_in_progress, object->debug_id);
+}
+
+/*
+ * release an operation
+ * - queues pending ops if this is the last in-progress op
+ */
+void fscache_put_operation(struct fscache_operation *op)
+{
+ struct fscache_object *object;
+
+ _enter("{%d}", atomic_read(&op->usage));
+
+ ASSERTCMP(atomic_read(&op->usage), >, 0);
+
+ if (!atomic_dec_and_test(&op->usage))
+ return;
+
+ _debug("PUT OP");
+ fscache_stat(&fscache_n_op_release);
+
+ if (op->release)
+ op->release(op);
+
+ object = op->object;
+ spin_lock(&object->lock);
+ if (test_bit(FSCACHE_OP_EXCLUSIVE, &op->flags)) {
+ ASSERTCMP(object->n_exclusive, >, 0);
+ object->n_exclusive--;
+ }
+
+ ASSERTCMP(object->n_in_progress, >, 0);
+ object->n_in_progress--;
+ if (object->n_in_progress == 0)
+ fscache_start_operations(object);
+
+ ASSERTCMP(object->n_ops, >, 0);
+ object->n_ops--;
+ if (object->n_ops == 0)
+ fscache_raise_event(object, FSCACHE_OBJECT_EV_CLEARED);
+
+ spin_unlock(&object->lock);
+ _leave("");
+}
+
+EXPORT_SYMBOL(fscache_put_operation);
+
+/*
+ * add object to queue
+ * - caller must hold fscache_thread_lock
+ */
+static void __fscache_enqueue_object(struct fscache_object *object)
+{
+ if (test_bit(FSCACHE_OBJECT_SYNC, &object->flags))
+ list_add_tail(&object->work_link, &fscache_sync_object_fifo);
+ else
+ list_add_tail(&object->work_link, &fscache_object_fifo);
+}
+
+/*
+ * enqueue an object for metadata-type processing
+ */
+void fscache_enqueue_object(struct fscache_object *object)
+{
+ struct fscache_cache *cache;
+ bool added;
+
+ _enter("{OBJ%x}", object->debug_id);
+
+ if (list_empty(&object->work_link) &&
+ !test_bit(FSCACHE_OBJECT_EV_REQUEUE, &object->events)) {
+ added = false;
+ spin_lock_irq(&fscache_object_lock);
+ if (!test_bit(FSCACHE_OBJECT_LOCK, &object->flags)) {
+ if (list_empty(&object->work_link)) {
+ _debug("add");
+ cache = object->cache;
+ atomic_inc(&cache->thread_usage);
+ cache->ops->grab_object(object);
+ __fscache_enqueue_object(object);
+ added = true;
+ }
+ } else {
+ _debug("defer");
+ set_bit(FSCACHE_OBJECT_EV_REQUEUE, &object->events);
+ }
+ spin_unlock_irq(&fscache_object_lock);
+ if (added) {
+ _debug("wake");
+ wake_up(&fscache_slow_threads);
+ }
+ }
+}
+
+/*
+ * enqueue the dependents of an object for metadata-type processing
+ * - the caller must hold the object's lock
+ * - this may cause an already locked object to wind up being processed again
+ */
+void fscache_enqueue_dependents(struct fscache_object *object)
+{
+ struct fscache_object *dep;
+
+ _enter("{%p}", object);
+
+ if (list_empty(&object->dependents))
+ return;
+
+ spin_lock_irq(&fscache_object_lock);
+
+ while (!list_empty(&object->dependents)) {
+ dep = list_entry(object->dependents.next,
+ struct fscache_object, work_link);
+ list_del(&dep->work_link);
+
+ clear_bit(FSCACHE_OBJECT_WAITING, &object->flags);
+
+ /* sort onto appropriate lists */
+ __fscache_enqueue_object(dep);
+
+ if (!list_empty(&object->dependents) && need_resched()) {
+ spin_unlock_irq(&fscache_object_lock);
+ cond_resched();
+ spin_lock_irq(&fscache_object_lock);
+ }
+ }
+
+ spin_unlock_irq(&fscache_object_lock);
+ wake_up(&fscache_slow_threads);
+}
+
+/*
+ * remove an object from whatever queue it's waiting on
+ */
+void fscache_dequeue_object(struct fscache_object *object)
+{
+ _enter("{OBJ%x}", object->debug_id);
+
+ if (!list_empty(&object->dependents)) {
+ spin_lock_irq(&fscache_object_lock);
+ list_del_init(&object->work_link);
+ spin_unlock_irq(&fscache_object_lock);
+ }
+ _leave("");
+}
+
+/*
+ * boost an object that's being waited upon by moving it to the priority queue
+ */
+void fscache_boost_object(struct fscache_object *object)
+{
+ set_bit(FSCACHE_OBJECT_BOOSTED, &object->flags);
+ if (!test_bit(FSCACHE_OBJECT_WAITING, &object->flags)) {
+ fscache_stat(&fscache_n_object_boosted);
+ spin_lock_irq(&fscache_object_lock);
+ if (!list_empty(&object->work_link))
+ list_move_tail(&object->work_link,
+ &fscache_sync_object_fifo);
+ spin_unlock_irq(&fscache_object_lock);
+ }
+}
+
+/*
+ * object dispatcher
+ * - slow threads take from the object FIFO by preference
+ * - called with fscache_thread_lock locked, which it drops
+ */
+static void fscache_dispatch_object(unsigned thread)
+{
+ struct fscache_object *object;
+ struct fscache_cache *cache = NULL;
+ unsigned long start;
+ bool sync;
+
+ spin_lock_irq(&fscache_object_lock);
+
+ if (list_empty(&fscache_object_fifo) &&
+ list_empty(&fscache_sync_object_fifo)) {
+ spin_unlock_irq(&fscache_object_lock);
+ return;
+ }
+
+ fscache_stat(&fscache_n_objs_processed[thread]);
+
+ sync = !list_empty(&fscache_sync_object_fifo);
+ if (sync) {
+ object = list_entry(fscache_sync_object_fifo.next,
+ struct fscache_object, work_link);
+ set_user_nice(current, -1);
+ } else {
+ object = list_entry(fscache_object_fifo.next,
+ struct fscache_object, work_link);
+ set_user_nice(current, 1);
+ }
+
+ /* lock the object so that it's only processed by one thread at
+ * once */
+ _debug("LK OBJ%x", object->debug_id);
+ list_del_init(&object->work_link);
+ if (test_and_set_bit(FSCACHE_OBJECT_LOCK, &object->flags)) {
+ _debug("OBJ%x already locked {%s,%lx}\n",
+ object->debug_id,
+ fscache_object_states[object->state],
+ object->events & object->event_mask);
+ set_bit(FSCACHE_OBJECT_EV_REQUEUE, &object->events);
+ spin_unlock_irq(&fscache_object_lock);
+ return;
+ }
+ do {
+ spin_unlock_irq(&fscache_object_lock);
+
+ do {
+ clear_bit(FSCACHE_OBJECT_EV_REQUEUE, &object->events);
+ start = jiffies;
+ fscache_object_state_machine(object);
+ fscache_hist(fscache_objs_histogram, start);
+ } while (object->events & object->event_mask);
+
+ spin_lock_irq(&fscache_object_lock);
+ } while (object->events & object->event_mask);
+
+ /* unlock the object */
+ _debug("UN OBJ%x", object->debug_id);
+ if (!test_and_clear_bit(FSCACHE_OBJECT_LOCK, &object->flags))
+ BUG();
+
+ spin_unlock_irq(&fscache_object_lock);
+
+ if (object) {
+ /* must do the wake up outside the thread pool lock to avoid a
+ * circular lock dependency against a __wake_up() lock in
+ * CacheFiles */
+ wake_up_bit(&object->lock, FSCACHE_OBJECT_LOCK);
+ cache = object->cache;
+ cache->ops->put_object(object);
+ _debug("%d REMAIN", atomic_read(&cache->thread_usage));
+ if (atomic_dec_and_test(&cache->thread_usage)) {
+ _debug("ALL GONE");
+ wake_up_all(&fscache_clearance_wq);
+ }
+ }
+}
+
+/*
+ * operation dispatcher
+ * - all threads can take from the fast FIFO
+ * - called with fscache_thread_lock locked, which it drops
+ */
+static void fscache_dispatch_operation(unsigned thread, struct list_head *queue)
+{
+ struct fscache_operation *op;
+ unsigned long start;
+
+ spin_lock_irq(&fscache_operation_lock);
+
+ if (list_empty(queue)) {
+ spin_unlock_irq(&fscache_operation_lock);
+ return;
+ }
+
+ fscache_stat(&fscache_n_ops_processed[thread]);
+
+ op = list_entry(queue->next, struct fscache_operation, work_link);
+
+ /* lock the operation so that it's only processed by one thread
+ * at once */
+ _debug("LK OP OBJ%x", op->object->debug_id);
+ if (test_and_set_bit(FSCACHE_OP_LOCK, &op->flags)) {
+ printk(KERN_ERR "FS-Cache: OP on OBJ%x already locked\n",
+ op->object->debug_id);
+ BUG();
+ }
+ list_del_init(&op->work_link);
+ spin_unlock_irq(&fscache_operation_lock);
+
+ ASSERT(op->processor != NULL);
+ start = jiffies;
+ op->processor(op);
+ fscache_hist(fscache_ops_histogram, start);
+
+ /* unlock the op and requeue if requested */
+ spin_lock_irq(&fscache_operation_lock);
+ _debug("UN OP OBJ%x", op->object->debug_id);
+ clear_bit(FSCACHE_OP_LOCK, &op->flags);
+
+ if (test_and_clear_bit(FSCACHE_OP_REQUEUE, &op->flags)) {
+ list_add(&op->work_link, queue);
+ op = NULL;
+ }
+
+ spin_unlock_irq(&fscache_operation_lock);
+
+ if (op)
+ fscache_put_operation(op);
+}
+
+/*
+ * thread dispatcher
+ */
+static void fscache_dispatch(unsigned thread)
+{
+ _enter("");
+
+ switch (thread % 3) {
+ case 2:
+ /* threads 2, 5, 8, 11, ... do objects, async ops then sync
+ * ops */
+ if (!list_empty(&fscache_object_fifo) ||
+ !list_empty(&fscache_sync_object_fifo)) {
+ fscache_dispatch_object(thread);
+ break;
+ }
+ case 1:
+ /* threads 1, 4, 7, 10, ... do async ops then sync ops */
+ if (!list_empty(&fscache_op_fifo)) {
+ fscache_dispatch_operation(thread, &fscache_op_fifo);
+ break;
+ }
+ if (!list_empty(&fscache_sync_op_fifo)) {
+ fscache_dispatch_operation(thread,
+ &fscache_sync_op_fifo);
+ break;
+ }
+ break;
+ default:
+ /* threads 0, 3, 6, 9, ... do sync ops then async ops */
+ if (!list_empty(&fscache_sync_op_fifo)) {
+ fscache_dispatch_operation(thread,
+ &fscache_sync_op_fifo);
+ break;
+ }
+ break;
+ }
+
+ _leave("");
+}
+
+/*
+ * operation dispatcher thread dedicated to doing fast ops
+ */
+static int kfscached_fast(unsigned thread)
+{
+ DECLARE_WAITQUEUE(myself, current);
+
+ do {
+ while (!list_empty(&fscache_op_fifo) ||
+ !list_empty(&fscache_sync_op_fifo)) {
+ fscache_dispatch(thread);
+ cond_resched();
+ }
+
+ add_wait_queue(&fscache_fast_threads, &myself);
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (list_empty(&fscache_op_fifo) &&
+ list_empty(&fscache_sync_op_fifo) &&
+ !kthread_should_stop())
+ schedule();
+ remove_wait_queue(&fscache_fast_threads, &myself);
+ __set_current_state(TASK_RUNNING);
+ } while (!kthread_should_stop());
+
+ return 0;
+}
+
+/*
+ * operation dispatcher thread for preferentially doing slow metadata ops (but
+ * can also do fast ops if bored)
+ */
+static int kfscached(void *_thread)
+{
+ unsigned thread = (unsigned long) _thread;
+
+ DECLARE_WAITQUEUE(myself_fast, current);
+ DECLARE_WAITQUEUE(myself_slow, current);
+
+ if (thread % 3 != 2) {
+ //if (thread % 3 == 1)
+ //set_user_nice(current, -4);
+ return kfscached_fast(thread);
+ }
+
+ set_user_nice(current, 1);
+
+ do {
+ while (!list_empty(&fscache_object_fifo) ||
+ !list_empty(&fscache_sync_object_fifo) ||
+ !list_empty(&fscache_op_fifo) ||
+ !list_empty(&fscache_sync_op_fifo)) {
+ fscache_dispatch(thread);
+ cond_resched();
+ }
+
+ add_wait_queue(&fscache_slow_threads, &myself_slow);
+ add_wait_queue_tail(&fscache_fast_threads, &myself_fast);
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (list_empty(&fscache_object_fifo) &&
+ list_empty(&fscache_sync_object_fifo) &&
+ list_empty(&fscache_op_fifo) &&
+ list_empty(&fscache_sync_op_fifo) &&
+ !kthread_should_stop())
+ schedule();
+ remove_wait_queue(&fscache_fast_threads, &myself_fast);
+ remove_wait_queue(&fscache_slow_threads, &myself_slow);
+ __set_current_state(TASK_RUNNING);
+ } while (!kthread_should_stop());
+
+ return 0;
+}
+
+/*
+ * initialise the worker thread pool
+ */
+int fscache_init_threads(void)
+{
+ static bool inited = false;
+ struct task_struct *t;
+ unsigned long loop, max;
+ int ret;
+
+ _enter("");
+
+ mutex_lock(&fscache_thread_mutex);
+
+ if (!inited) {
+ max = fscache_n_threads;
+ if (max < FSCACHE_MIN_THREADS)
+ max = FSCACHE_MIN_THREADS;
+ else if (max > FSCACHE_MAX_THREADS)
+ max = FSCACHE_MAX_THREADS;
+
+ for (loop = 0; loop < max; loop++) {
+ t = kthread_create(kfscached, (void *) loop,
+ "kfsc%02ud", loop);
+ if (IS_ERR(t))
+ goto failed;
+ fscache_threads[loop] = t;
+ wake_up_process(t);
+ }
+
+ inited = true;
+ }
+ mutex_unlock(&fscache_thread_mutex);
+ _leave(" = 0");
+ return 0;
+
+failed:
+ ret = PTR_ERR(t);
+ printk(KERN_ERR "FS-Cache: Unable to create kfscached threads (%d)\n",
+ ret);
+
+ while (loop-- > 0) {
+ if (fscache_threads[loop]) {
+ kthread_stop(fscache_threads[loop]);
+ fscache_threads[loop] = NULL;
+ }
+ }
+
+ mutex_unlock(&fscache_thread_mutex);
+ _leave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * kill the pool of running threads
+ */
+void fscache_kill_threads(void)
+{
+ unsigned int loop;
+
+ _enter("");
+ mutex_lock(&fscache_thread_mutex);
+
+ for (loop = 0; loop < FSCACHE_MAX_THREADS; loop++)
+ if (fscache_threads[loop])
+ kthread_stop(fscache_threads[loop]);
+
+ BUG_ON(!list_empty(&fscache_object_fifo));
+ BUG_ON(!list_empty(&fscache_sync_object_fifo));
+ BUG_ON(!list_empty(&fscache_op_fifo));
+ BUG_ON(!list_empty(&fscache_sync_op_fifo));
+
+ mutex_unlock(&fscache_thread_mutex);
+ _leave("");
+}
diff --git a/include/linux/fscache-cache.h b/include/linux/fscache-cache.h
new file mode 100644
index 0000000..176797f
--- /dev/null
+++ b/include/linux/fscache-cache.h
@@ -0,0 +1,433 @@
+/* General filesystem caching backing cache interface
+ *
+ * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * NOTE!!! See:
+ *
+ * Documentation/filesystems/caching/backend-api.txt
+ *
+ * for a description of the cache backend interface declared here.
+ */
+
+#ifndef _LINUX_FSCACHE_CACHE_H
+#define _LINUX_FSCACHE_CACHE_H
+
+#include <linux/fscache.h>
+
+#define NR_MAXCACHES BITS_PER_LONG
+
+struct fscache_cache;
+struct fscache_cache_ops;
+struct fscache_object;
+
+#ifdef CONFIG_FSCACHE_PROC
+extern struct proc_dir_entry *proc_fscache;
+#endif
+
+/*
+ * cache tag definition
+ */
+struct fscache_cache_tag {
+ struct list_head link;
+ struct fscache_cache *cache; /* cache referred to by this tag */
+ unsigned long flags;
+#define FSCACHE_TAG_RESERVED 0 /* T if tag is reserved for a cache */
+ atomic_t usage;
+ char name[0]; /* tag name */
+};
+
+/*
+ * cache definition
+ */
+struct fscache_cache {
+ const struct fscache_cache_ops *ops;
+ struct fscache_cache_tag *tag; /* tag representing this cache */
+ struct kobject kobj; /* system representation of this cache */
+ struct list_head link; /* link in list of caches */
+ size_t max_index_size; /* maximum size of index data */
+ char identifier[32]; /* cache label */
+
+ /* node management */
+ struct list_head object_list; /* list of data/index objects */
+ spinlock_t object_list_lock;
+ atomic_t thread_usage; /* no. of threads working this cache */
+ struct fscache_object *fsdef; /* object for the fsdef index */
+ unsigned long flags;
+#define FSCACHE_IOERROR 0 /* cache stopped on I/O error */
+#define FSCACHE_CACHE_WITHDRAWN 1 /* cache has been withdrawn */
+};
+
+/*
+ * asynchronous operation being applied to or waiting to be applied to a cache
+ * object
+ * - slow operations are done in the context of the process that issued them,
+ * not in the context of kfscached
+ */
+struct fscache_operation {
+ struct list_head work_link; /* link in worker thread FIFO or
+ * link in object->pending_ops */
+ struct fscache_object *object; /* object to be operated upon */
+
+ unsigned long flags;
+#define FSCACHE_OP_WAITING 0 /* cleared when op is woken */
+#define FSCACHE_OP_SYNC 1 /* synchronous operation */
+#define FSCACHE_OP_EXCLUSIVE 2 /* exclusive op, other ops must wait */
+#define FSCACHE_OP_LOCK 3 /* thread pool processing lock */
+#define FSCACHE_OP_REQUEUE 4 /* op needs more processing */
+
+ atomic_t usage;
+
+ /* operation processor callback
+ * - can be NULL if FSCACHE_OP_WAITING is going to be used to perform
+ * the op in a non-pool thread */
+ void (*processor)(struct fscache_operation *op);
+
+ /* operation releaser */
+ void (*release)(struct fscache_operation *op);
+};
+
+extern void fscache_enqueue_operation(struct fscache_operation *);
+extern void fscache_put_operation(struct fscache_operation *);
+
+/*
+ * data read operation
+ */
+struct fscache_retrieval {
+ struct fscache_operation op;
+ struct address_space *mapping; /* netfs pages */
+ fscache_rw_complete_t end_io_func; /* function to call on I/O completion */
+ void *context; /* netfs read context (pinned) */
+ struct list_head to_do; /* list of things to be done by the backend */
+ unsigned long start_time; /* time at which retrieval started */
+};
+
+typedef int (*fscache_page_retrieval_func_t)(struct fscache_retrieval *op,
+ struct page *page,
+ gfp_t gfp);
+
+typedef int (*fscache_pages_retrieval_func_t)(struct fscache_retrieval *op,
+ struct list_head *pages,
+ unsigned *nr_pages,
+ gfp_t gfp);
+
+/**
+ * fscache_get_retrieval - Get an extra reference on a retrieval operation
+ * @op: The retrieval operation to get a reference on
+ *
+ * Get an extra reference on a retrieval operation.
+ */
+static inline
+struct fscache_retrieval *fscache_get_retrieval(struct fscache_retrieval *op)
+{
+ atomic_inc(&op->op.usage);
+ return op;
+}
+
+/**
+ * fscache_enqueue_retrieval - Enqueue a retrieval operation for processing
+ * @op: The retrieval operation affected
+ *
+ * Enqueue a retrieval operation for processing by the FS-Cache thread pool.
+ */
+static inline void fscache_enqueue_retrieval(struct fscache_retrieval *op)
+{
+ fscache_enqueue_operation(&op->op);
+}
+
+/**
+ * fscache_put_retrieval - Drop a reference to a retrieval operation
+ * @op: The retrieval operation affected
+ *
+ * Drop a reference to a retrieval operation.
+ */
+static inline void fscache_put_retrieval(struct fscache_retrieval *op)
+{
+ fscache_put_operation(&op->op);
+}
+
+/*
+ * cached page storage work item
+ * - used to do three things:
+ * - batch writes to the cache
+ * - do cache writes asynchronously
+ * - defer writes until cache object lookup completion
+ */
+struct fscache_storage {
+ struct fscache_operation op;
+ pgoff_t store_limit; /* don't write more than this */
+};
+
+/*
+ * cache operations
+ */
+struct fscache_cache_ops {
+ /* name of cache provider */
+ const char *name;
+
+ /* allocate an object record for a cookie */
+ struct fscache_object *(*alloc_object)(struct fscache_cache *cache,
+ struct fscache_cookie *cookie);
+
+ /* look up the object for a cookie */
+ void (*lookup_object)(struct fscache_object *object);
+
+ /* finished looking up */
+ void (*lookup_complete)(struct fscache_object *object);
+
+ /* increment the usage count on this object (may fail if unmounting) */
+ struct fscache_object *(*grab_object)(struct fscache_object *object);
+
+ /* pin an object in the cache */
+ int (*pin_object)(struct fscache_object *object);
+
+ /* unpin an object in the cache */
+ void (*unpin_object)(struct fscache_object *object);
+
+ /* store the updated auxilliary data on an object */
+ void (*update_object)(struct fscache_object *object);
+
+ /* discard the resources pinned by an object and effect retirement if
+ * necessary */
+ void (*drop_object)(struct fscache_object *object);
+
+ /* dispose of a reference to an object */
+ void (*put_object)(struct fscache_object *object);
+
+ /* sync a cache */
+ void (*sync_cache)(struct fscache_cache *cache);
+
+ /* notification that the attributes of a non-index object (such as
+ * i_size) have changed */
+ int (*attr_changed)(struct fscache_object *object);
+
+ /* reserve space for an object's data and associated metadata */
+ int (*reserve_space)(struct fscache_object *object, loff_t i_size);
+
+ /* request a backing block for a page be read or allocated in the
+ * cache */
+ fscache_page_retrieval_func_t read_or_alloc_page;
+
+ /* request backing blocks for a list of pages be read or allocated in
+ * the cache */
+ fscache_pages_retrieval_func_t read_or_alloc_pages;
+
+ /* request a backing block for a page be allocated in the cache so that
+ * it can be written directly */
+ fscache_page_retrieval_func_t allocate_page;
+
+ /* request backing blocks for pages be allocated in the cache so that
+ * they can be written directly */
+ fscache_pages_retrieval_func_t allocate_pages;
+
+ /* write a page to its backing block in the cache */
+ int (*write_page)(struct fscache_storage *op, struct page *page);
+
+ /* detach backing block from a page (optional)
+ * - must release the cookie lock before returning
+ * - may sleep
+ */
+ void (*uncache_page)(struct fscache_object *object,
+ struct page *page);
+
+ /* dissociate a cache from all the pages it was backing */
+ void (*dissociate_pages)(struct fscache_cache *cache);
+};
+
+/*
+ * data file or index object cookie
+ * - a file will only appear in one cache
+ * - a request to cache a file may or may not be honoured, subject to
+ * constraints such as disk space
+ * - indices are created on disk just-in-time
+ */
+struct fscache_cookie {
+ atomic_t usage; /* number of users of this cookie */
+ atomic_t n_children; /* number of children of this cookie */
+ spinlock_t lock;
+ struct hlist_head backing_objects; /* object(s) backing this file/index */
+ const struct fscache_cookie_def *def; /* definition */
+ struct fscache_cookie *parent; /* parent of this entry */
+ void *netfs_data; /* back pointer to netfs */
+ unsigned long flags;
+#define FSCACHE_COOKIE_LOOKING_UP 0 /* T if non-index cookie being looked up still */
+#define FSCACHE_COOKIE_CREATING 1 /* T if non-index object being created still */
+#define FSCACHE_COOKIE_NO_DATA_YET 2 /* T if new object with no cached data yet */
+#define FSCACHE_COOKIE_PENDING_FILL 3 /* T if pending initial fill on object */
+#define FSCACHE_COOKIE_FILLING 4 /* T if filling object incrementally */
+#define FSCACHE_COOKIE_UNAVAILABLE 5 /* T if cookie is unavailable (error, etc) */
+};
+
+extern struct fscache_cookie fscache_fsdef_index;
+
+/*
+ * on-disk cache file or index handle
+ */
+struct fscache_object {
+ enum {
+ FSCACHE_OBJECT_INIT, /* object in initial unbound state */
+ FSCACHE_OBJECT_LOOKING_UP, /* looking up object */
+ FSCACHE_OBJECT_CREATING, /* creating object */
+
+ /* active states */
+ FSCACHE_OBJECT_AVAILABLE, /* cleaning up object after creation */
+ FSCACHE_OBJECT_ACTIVE, /* object is usable */
+ FSCACHE_OBJECT_UPDATING, /* object is updating */
+
+ /* terminal states */
+ FSCACHE_OBJECT_DYING, /* object waiting for accessors to finish */
+ FSCACHE_OBJECT_LC_DYING, /* object cleaning up after lookup/create */
+ FSCACHE_OBJECT_ABORT_INIT, /* abort the init state */
+ FSCACHE_OBJECT_RELEASING, /* releasing object */
+ FSCACHE_OBJECT_RECYCLING, /* retiring object */
+ FSCACHE_OBJECT_WITHDRAWING, /* withdrawing object */
+ FSCACHE_OBJECT_DEAD, /* object is now dead */
+ } state;
+
+ int debug_id; /* debugging ID */
+ int n_children; /* number of child objects */
+ int n_ops; /* number of ops outstanding on object */
+ int n_in_progress; /* number of ops in progress */
+ int n_exclusive; /* number of exclusive ops queued */
+ spinlock_t lock; /* state and operations lock */
+
+ unsigned long lookup_jif; /* time at which lookup started */
+ unsigned long event_mask; /* events this object is interested in */
+ unsigned long events; /* events to be processed by this object
+ * (order is important - using fls) */
+#define FSCACHE_OBJECT_EV_REQUEUE 0 /* T if object should be requeued */
+#define FSCACHE_OBJECT_EV_UPDATE 1 /* T if object should be updated */
+#define FSCACHE_OBJECT_EV_CLEARED 2 /* T if accessors all gone */
+#define FSCACHE_OBJECT_EV_ERROR 3 /* T if fatal error occurred during processing */
+#define FSCACHE_OBJECT_EV_RELEASE 4 /* T if netfs requested object release */
+#define FSCACHE_OBJECT_EV_RETIRE 5 /* T if netfs requested object retirement */
+#define FSCACHE_OBJECT_EV_WITHDRAW 6 /* T if cache requested object withdrawal */
+
+ unsigned long flags;
+#define FSCACHE_OBJECT_LOCK 0 /* T if object is busy being processed */
+#define FSCACHE_OBJECT_SYNC 1 /* T if object is has waiters */
+#define FSCACHE_OBJECT_PENDING_WRITE 2 /* T if object has pending write */
+#define FSCACHE_OBJECT_WAITING 3 /* T if object is waiting on its parent */
+#define FSCACHE_OBJECT_BOOSTED 4 /* T if object was boosted to priority queue */
+
+ struct list_head cache_link; /* link in cache->object_list */
+ struct hlist_node cookie_link; /* link in cookie->backing_objects */
+ struct fscache_cache *cache; /* cache that supplied this object */
+ struct fscache_cookie *cookie; /* netfs's file/index object */
+ struct fscache_object *parent; /* parent object */
+ struct list_head work_link; /* link in worker thread FIFO */
+ struct list_head dependents; /* FIFO of dependent objects */
+ struct list_head pending_ops; /* unstarted operations on this object */
+ struct radix_tree_root stores; /* data to be stored */
+ pgoff_t store_limit; /* current storage limit */
+};
+
+extern const char *fscache_object_states[];
+
+#define fscache_object_is_active(obj) \
+ (!test_bit(FSCACHE_IOERROR, &(obj)->cache->flags) && \
+ (obj)->state >= FSCACHE_OBJECT_AVAILABLE && \
+ (obj)->state < FSCACHE_OBJECT_DYING)
+
+/**
+ * fscache_object_init - Initialise a cache object description
+ * @object: Object description
+ *
+ * Initialise a cache object description to its basic values.
+ *
+ * See Documentation/filesystems/caching/backend-api.txt for a complete
+ * description.
+ */
+static inline
+void fscache_object_init(struct fscache_object *object)
+{
+ object->state = FSCACHE_OBJECT_INIT;
+ spin_lock_init(&object->lock);
+ INIT_LIST_HEAD(&object->cache_link);
+ INIT_HLIST_NODE(&object->cookie_link);
+ INIT_LIST_HEAD(&object->work_link);
+ INIT_LIST_HEAD(&object->dependents);
+ INIT_LIST_HEAD(&object->pending_ops);
+ INIT_RADIX_TREE(&object->stores, GFP_NOFS);
+ object->n_children = 0;
+ object->n_ops = object->n_in_progress = object->n_exclusive = 0;
+ object->events = object->event_mask = 0;
+ object->flags = 0;
+ object->store_limit = 0;
+ object->cache = NULL;
+ object->cookie = NULL;
+}
+
+extern void fscache_object_lookup_negative(struct fscache_object *object);
+extern void fscache_obtained_object(struct fscache_object *object);
+
+/**
+ * fscache_object_lookup_error - Note an object encountered an error
+ * @object: The object on which the error was encountered
+ *
+ * Note that an object encountered a fatal error (usually an I/O error) and
+ * that it should be withdrawn as soon as possible.
+ */
+static inline void fscache_object_lookup_error(struct fscache_object *object)
+{
+ set_bit(FSCACHE_OBJECT_EV_ERROR, &object->events);
+}
+
+/**
+ * fscache_set_store_limit - Set the maximum size to be stored in an object
+ * @object: The object to set the maximum on
+ * @i_size: The limit to set in bytes
+ *
+ * Set the maximum size an object is permitted to reach, implying the highest
+ * byte that may be written. Intended to be called by the attr_changed() op.
+ *
+ * See Documentation/filesystems/caching/backend-api.txt for a complete
+ * description.
+ */
+static inline
+void fscache_set_store_limit(struct fscache_object *object, loff_t i_size)
+{
+ object->store_limit = i_size >> PAGE_SHIFT;
+ if (i_size & ~PAGE_MASK)
+ object->store_limit++;
+}
+
+/**
+ * fscache_end_io - End a retrieval operation on a page
+ * @op: The FS-Cache operation covering the retrieval
+ * @page: The page that was to be fetched
+ * @error: The error code (0 if successful)
+ *
+ * Note the end of an operation to retrieve a page, as covered by a particular
+ * operation record.
+ */
+static inline void fscache_end_io(struct fscache_retrieval *op,
+ struct page *page, int error)
+{
+ op->end_io_func(page, op->context, error);
+}
+
+/*
+ * out-of-line cache backend functions
+ */
+extern void fscache_init_cache(struct fscache_cache *cache,
+ const struct fscache_cache_ops *ops,
+ const char *idfmt,
+ ...) __attribute__ ((format (printf, 3, 4)));
+
+extern int fscache_add_cache(struct fscache_cache *cache,
+ struct fscache_object *fsdef,
+ const char *tagname);
+extern void fscache_withdraw_cache(struct fscache_cache *cache);
+
+extern void fscache_io_error(struct fscache_cache *cache);
+
+extern void fscache_mark_pages_cached(struct fscache_retrieval *op,
+ struct pagevec *pagevec);
+
+#endif /* _LINUX_FSCACHE_CACHE_H */
diff --git a/include/linux/fscache.h b/include/linux/fscache.h
new file mode 100644
index 0000000..f9a074b
--- /dev/null
+++ b/include/linux/fscache.h
@@ -0,0 +1,593 @@
+/* General filesystem caching interface
+ *
+ * Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * NOTE!!! See:
+ *
+ * Documentation/filesystems/caching/netfs-api.txt
+ *
+ * for a description of the network filesystem interface declared here.
+ */
+
+#ifndef _LINUX_FSCACHE_H
+#define _LINUX_FSCACHE_H
+
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/pagemap.h>
+#include <linux/pagevec.h>
+
+#if defined(CONFIG_FSCACHE) || defined(CONFIG_FSCACHE_MODULE)
+#define fscache_available() (1)
+#define fscache_cookie_valid(cookie) (cookie)
+#else
+#define fscache_available() (0)
+#define fscache_cookie_valid(cookie) (0)
+#endif
+
+
+/* pattern used to fill dead space in an index entry */
+#define FSCACHE_INDEX_DEADFILL_PATTERN 0x79
+
+struct pagevec;
+struct fscache_cache_tag;
+struct fscache_cookie;
+struct fscache_netfs;
+struct fscache_netfs_operations;
+
+typedef void (*fscache_rw_complete_t)(struct page *page,
+ void *context,
+ int error);
+
+/* result of index entry consultation */
+typedef enum {
+ FSCACHE_CHECKAUX_OKAY, /* entry okay as is */
+ FSCACHE_CHECKAUX_NEEDS_UPDATE, /* entry requires update */
+ FSCACHE_CHECKAUX_OBSOLETE, /* entry requires deletion */
+} fscache_checkaux_t;
+
+/*
+ * fscache cookie definition
+ */
+struct fscache_cookie_def
+{
+ /* name of cookie type */
+ char name[16];
+
+ /* cookie type */
+ uint8_t type;
+#define FSCACHE_COOKIE_TYPE_INDEX 0
+#define FSCACHE_COOKIE_TYPE_DATAFILE 1
+
+ /* select the cache into which to insert an entry in this index
+ * - optional
+ * - should return a cache identifier or NULL to cause the cache to be
+ * inherited from the parent if possible or the first cache picked
+ * for a non-index file if not
+ */
+ struct fscache_cache_tag *(*select_cache)(const void *parent_netfs_data,
+ const void *cookie_netfs_data);
+
+ /* get an index key
+ * - should store the key data in the buffer
+ * - should return the amount of amount stored
+ * - not permitted to return an error
+ * - the netfs data from the cookie being used as the source is
+ * presented
+ */
+ uint16_t (*get_key)(const void *cookie_netfs_data,
+ void *buffer,
+ uint16_t bufmax);
+
+ /* get certain file attributes from the netfs data
+ * - this function can be absent for an index
+ * - not permitted to return an error
+ * - the netfs data from the cookie being used as the source is
+ * presented
+ */
+ void (*get_attr)(const void *cookie_netfs_data, uint64_t *size);
+
+ /* get the auxilliary data from netfs data
+ * - this function can be absent if the index carries no state data
+ * - should store the auxilliary data in the buffer
+ * - should return the amount of amount stored
+ * - not permitted to return an error
+ * - the netfs data from the cookie being used as the source is
+ * presented
+ */
+ uint16_t (*get_aux)(const void *cookie_netfs_data,
+ void *buffer,
+ uint16_t bufmax);
+
+ /* consult the netfs about the state of an object
+ * - this function can be absent if the index carries no state data
+ * - the netfs data from the cookie being used as the target is
+ * presented, as is the auxilliary data
+ */
+ fscache_checkaux_t (*check_aux)(void *cookie_netfs_data,
+ const void *data,
+ uint16_t datalen);
+
+ /* get an extra reference on a read context
+ * - this function can be absent if the completion function doesn't
+ * require a context
+ */
+ void (*get_context)(void *cookie_netfs_data, void *context);
+
+ /* release an extra reference on a read context
+ * - this function can be absent if the completion function doesn't
+ * require a context
+ */
+ void (*put_context)(void *cookie_netfs_data, void *context);
+
+ /* indicate pages that now have cache metadata retained
+ * - this function should mark the specified pages as now being cached
+ * - the pages will have been marked with PG_fscache before this is
+ * called, so this is optional
+ */
+ void (*mark_pages_cached)(void *cookie_netfs_data,
+ struct address_space *mapping,
+ struct pagevec *cached_pvec);
+
+ /* indicate the cookie is no longer cached
+ * - this function is called when the backing store currently caching
+ * a cookie is removed
+ * - the netfs should use this to clean up any markers indicating
+ * cached pages
+ * - this is mandatory for any object that may have data
+ */
+ void (*now_uncached)(void *cookie_netfs_data);
+};
+
+/*
+ * netfs operations pointer (currently there aren't any ops)
+ */
+struct fscache_netfs_operations
+{
+};
+
+/*
+ * fscache cached network filesystem type
+ * - name, version and ops must be filled in before registration
+ * - all other fields will be set during registration
+ */
+struct fscache_netfs
+{
+ uint32_t version; /* indexing version */
+ const char *name; /* filesystem name */
+ struct fscache_cookie *primary_index;
+ const struct fscache_netfs_operations *ops;
+ struct list_head link; /* internal link */
+};
+
+/*
+ * slow-path functions for when there is actually caching available, and the
+ * netfs does actually have a valid token
+ * - these are not to be called directly
+ * - these are undefined symbols when FS-Cache is not configured and the
+ * optimiser takes care of not using them
+ */
+extern int __fscache_register_netfs(struct fscache_netfs *);
+extern void __fscache_unregister_netfs(struct fscache_netfs *);
+extern struct fscache_cache_tag *__fscache_lookup_cache_tag(const char *);
+extern void __fscache_release_cache_tag(struct fscache_cache_tag *);
+extern struct fscache_cookie *__fscache_acquire_cookie(struct fscache_cookie *,
+ const struct fscache_cookie_def *,
+ void *);
+extern void __fscache_relinquish_cookie(struct fscache_cookie *, int);
+extern void __fscache_update_cookie(struct fscache_cookie *);
+extern int __fscache_pin_cookie(struct fscache_cookie *);
+extern void __fscache_unpin_cookie(struct fscache_cookie *);
+extern int __fscache_attr_changed(struct fscache_cookie *);
+extern int __fscache_reserve_space(struct fscache_cookie *, loff_t);
+extern int __fscache_read_or_alloc_page(struct fscache_cookie *,
+ struct page *,
+ fscache_rw_complete_t,
+ void *,
+ gfp_t);
+extern int __fscache_read_or_alloc_pages(struct fscache_cookie *,
+ struct address_space *,
+ struct list_head *,
+ unsigned *,
+ fscache_rw_complete_t,
+ void *,
+ gfp_t);
+extern int __fscache_alloc_page(struct fscache_cookie *, struct page *, gfp_t);
+extern int __fscache_write_page(struct fscache_cookie *, struct page *, gfp_t);
+
+extern int __fscache_write_pages(struct fscache_cookie *,
+ struct pagevec *,
+ fscache_rw_complete_t,
+ void *,
+ gfp_t);
+extern void __fscache_uncache_page(struct fscache_cookie *, struct page *);
+extern void __fscache_uncache_pages(struct fscache_cookie *, struct pagevec *);
+
+/**
+ * fscache_register_netfs - Register a filesystem as desiring caching services
+ * @netfs: The description of the filesystem
+ *
+ * Register a filesystem as desiring caching services if they're available.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+int fscache_register_netfs(struct fscache_netfs *netfs)
+{
+ if (fscache_available())
+ return __fscache_register_netfs(netfs);
+ else
+ return 0;
+}
+
+/**
+ * fscache_unregister_netfs - Indicate that a filesystem no longer desires
+ * caching services
+ * @netfs: The description of the filesystem
+ *
+ * Indicate that a filesystem no longer desires caching services for the
+ * moment.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+void fscache_unregister_netfs(struct fscache_netfs *netfs)
+{
+ if (fscache_available())
+ __fscache_unregister_netfs(netfs);
+}
+
+/**
+ * fscache_lookup_cache_tag - Look up a cache tag
+ * @name: The name of the tag to search for
+ *
+ * Acquire a specific cache referral tag that can be used to select a specific
+ * cache in which to cache an index.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+struct fscache_cache_tag *fscache_lookup_cache_tag(const char *name)
+{
+ if (fscache_available())
+ return __fscache_lookup_cache_tag(name);
+ else
+ return NULL;
+}
+
+/**
+ * fscache_release_cache_tag - Release a cache tag
+ * @tag: The tag to release
+ *
+ * Release a reference to a cache referral tag previously looked up.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+void fscache_release_cache_tag(struct fscache_cache_tag *tag)
+{
+ if (fscache_available())
+ __fscache_release_cache_tag(tag);
+}
+
+/**
+ * fscache_acquire_cookie - Acquire a cookie to represent a cache object
+ * @parent: The cookie that's to be the parent of this one
+ * @def: A description of the cache object, including callback operations
+ * @netfs_data: An arbitrary piece of data to be kept in the cookie to
+ * represent the cache object to the netfs
+ *
+ * This function is used to inform FS-Cache about part of an index hierarchy
+ * that can be used to locate files. This is done by requesting a cookie for
+ * each index in the path to the file.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+struct fscache_cookie *fscache_acquire_cookie(struct fscache_cookie *parent,
+ const struct fscache_cookie_def *def,
+ void *netfs_data)
+{
+ if (fscache_cookie_valid(parent))
+ return __fscache_acquire_cookie(parent, def, netfs_data);
+ else
+ return NULL;
+}
+
+/**
+ * fscache_relinquish_cookie - Return the cookie to the cache, maybe discarding
+ * it
+ * @cookie: The cookie being returned
+ * @retire: True if the cache object the cookie represents is to be discarded
+ *
+ * This function returns a cookie to the cache, forcibly discarding the
+ * associated cache object if retire is set to true.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+void fscache_relinquish_cookie(struct fscache_cookie *cookie, int retire)
+{
+ if (fscache_cookie_valid(cookie))
+ __fscache_relinquish_cookie(cookie, retire);
+}
+
+/**
+ * fscache_update_cookie - Request that a cache object be updated
+ * @cookie: The cookie representing the cache object
+ *
+ * Request an update of the index data for the cache object associated with the
+ * cookie.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+void fscache_update_cookie(struct fscache_cookie *cookie)
+{
+ if (fscache_cookie_valid(cookie))
+ __fscache_update_cookie(cookie);
+}
+
+/**
+ * fscache_pin_cookie - Pin a data-storage cache object in its cache
+ * @cookie: The cookie representing the cache object
+ *
+ * Permit data-storage cache objects to be pinned in the cache.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+int fscache_pin_cookie(struct fscache_cookie *cookie)
+{
+ if (fscache_cookie_valid(cookie))
+ return __fscache_pin_cookie(cookie);
+ else
+ return -ENOBUFS;
+}
+
+/**
+ * fscache_pin_cookie - Unpin a data-storage cache object in its cache
+ * @cookie: The cookie representing the cache object
+ *
+ * Permit data-storage cache objects to be unpinned from the cache.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+void fscache_unpin_cookie(struct fscache_cookie *cookie)
+{
+ if (fscache_cookie_valid(cookie))
+ __fscache_unpin_cookie(cookie);
+}
+
+/**
+ * fscache_attr_changed - Notify cache that an object's attributes changed
+ * @cookie: The cookie representing the cache object
+ *
+ * Send a notification to the cache indicating that an object's attributes have
+ * changed. This includes the data size. These attributes will be obtained
+ * through the get_attr() cookie definition op.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+int fscache_attr_changed(struct fscache_cookie *cookie)
+{
+ if (fscache_cookie_valid(cookie))
+ return __fscache_attr_changed(cookie);
+ else
+ return -ENOBUFS;
+}
+
+/**
+ * fscache_reserve_space - Reserve data space for a cached object
+ * @cookie: The cookie representing the cache object
+ * @i_size: The amount of space to be reserved
+ *
+ * Reserve an amount of space in the cache for the cache object attached to a
+ * cookie so that a write to that object within the space can always be
+ * honoured.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+int fscache_reserve_space(struct fscache_cookie *cookie, loff_t size)
+{
+ if (fscache_cookie_valid(cookie))
+ return __fscache_reserve_space(cookie, size);
+ else
+ return -ENOBUFS;
+}
+
+/**
+ * fscache_read_or_alloc_page - Read a page from the cache or allocate a block
+ * in which to store it
+ * @cookie: The cookie representing the cache object
+ * @page: The netfs page to fill if possible
+ * @end_io_func: The callback to invoke when and if the page is filled
+ * @context: An arbitrary piece of data to pass on to end_io_func()
+ * @gfp: The conditions under which memory allocation should be made
+ *
+ * Read a page from the cache, or if that's not possible make a potential
+ * one-block reservation in the cache into which the page may be stored once
+ * fetched from the server.
+ *
+ * If the page is not backed by the cache object, or if it there's some reason
+ * it can't be, -ENOBUFS will be returned and nothing more will be done for
+ * that page.
+ *
+ * Else, if that page is backed by the cache, a read will be initiated directly
+ * to the netfs's page and 0 will be returned by this function. The
+ * end_io_func() callback will be invoked when the operation terminates on a
+ * completion or failure. Note that the callback may be invoked before the
+ * return.
+ *
+ * Else, if the page is unbacked, -ENODATA is returned and a block may have
+ * been allocated in the cache.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+int fscache_read_or_alloc_page(struct fscache_cookie *cookie,
+ struct page *page,
+ fscache_rw_complete_t end_io_func,
+ void *context,
+ gfp_t gfp)
+{
+ if (fscache_cookie_valid(cookie))
+ return __fscache_read_or_alloc_page(cookie, page, end_io_func,
+ context, gfp);
+ else
+ return -ENOBUFS;
+}
+
+/**
+ * fscache_read_or_alloc_pages - Read pages from the cache and/or allocate
+ * blocks in which to store them
+ * @cookie: The cookie representing the cache object
+ * @mapping: The netfs inode mapping to which the pages will be attached
+ * @pages: A list of potential netfs pages to be filled
+ * @end_io_func: The callback to invoke when and if each page is filled
+ * @context: An arbitrary piece of data to pass on to end_io_func()
+ * @gfp: The conditions under which memory allocation should be made
+ *
+ * Read a set of pages from the cache, or if that's not possible, attempt to
+ * make a potential one-block reservation for each page in the cache into which
+ * that page may be stored once fetched from the server.
+ *
+ * If some pages are not backed by the cache object, or if it there's some
+ * reason they can't be, -ENOBUFS will be returned and nothing more will be
+ * done for that pages.
+ *
+ * Else, if some of the pages are backed by the cache, a read will be initiated
+ * directly to the netfs's page and 0 will be returned by this function. The
+ * end_io_func() callback will be invoked when the operation terminates on a
+ * completion or failure. Note that the callback may be invoked before the
+ * return.
+ *
+ * Else, if a page is unbacked, -ENODATA is returned and a block may have
+ * been allocated in the cache.
+ *
+ * Because the function may want to return all of -ENOBUFS, -ENODATA and 0 in
+ * regard to different pages, the return values are prioritised in that order.
+ * Any pages submitted for reading are removed from the pages list.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+int fscache_read_or_alloc_pages(struct fscache_cookie *cookie,
+ struct address_space *mapping,
+ struct list_head *pages,
+ unsigned *nr_pages,
+ fscache_rw_complete_t end_io_func,
+ void *context,
+ gfp_t gfp)
+{
+ if (fscache_cookie_valid(cookie))
+ return __fscache_read_or_alloc_pages(cookie, mapping, pages,
+ nr_pages, end_io_func,
+ context, gfp);
+ else
+ return -ENOBUFS;
+}
+
+/**
+ * fscache_alloc_page - Allocate a block in which to store a page
+ * @cookie: The cookie representing the cache object
+ * @page: The netfs page to allocate a page for
+ * @gfp: The conditions under which memory allocation should be made
+ *
+ * Request Allocation a block in the cache in which to store a netfs page
+ * without retrieving any contents from the cache.
+ *
+ * If the page is not backed by a file then -ENOBUFS will be returned and
+ * nothing more will be done, and no reservation will be made.
+ *
+ * Else, a block will be allocated if one wasn't already, and 0 will be
+ * returned
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+int fscache_alloc_page(struct fscache_cookie *cookie,
+ struct page *page,
+ gfp_t gfp)
+{
+ if (fscache_cookie_valid(cookie))
+ return __fscache_alloc_page(cookie, page, gfp);
+ else
+ return -ENOBUFS;
+}
+
+/**
+ * fscache_write_page - Request storage of a page in the cache
+ * @cookie: The cookie representing the cache object
+ * @page: The netfs page to store
+ * @gfp: The conditions under which memory allocation should be made
+ *
+ * Request the contents of the netfs page be written into the cache. This
+ * request may be ignored if no cache block is currently allocated, in which
+ * case it will return -ENOBUFS.
+ *
+ * If a cache block was already allocated, a write will be initiated and 0 will
+ * be returned. The PG_fscache_write page bit is set immediately and will then
+ * be cleared at the completion of the write to indicate the success or failure
+ * of the operation. Note that the completion may happen before the return.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+int fscache_write_page(struct fscache_cookie *cookie,
+ struct page *page,
+ gfp_t gfp)
+{
+ if (fscache_cookie_valid(cookie))
+ return __fscache_write_page(cookie, page, gfp);
+ else
+ return -ENOBUFS;
+}
+
+/**
+ * fscache_uncache_page - Indicate that caching is no longer required on a page
+ * @cookie: The cookie representing the cache object
+ * @page: The netfs page that was being cached.
+ *
+ * Tell the cache that we no longer want a page to be cached and that it should
+ * remove any knowledge of the netfs page it may have.
+ *
+ * Note that this cannot cancel any outstanding I/O operations between this
+ * page and the cache.
+ *
+ * See Documentation/filesystems/caching/netfs-api.txt for a complete
+ * description.
+ */
+static inline
+void fscache_uncache_page(struct fscache_cookie *cookie,
+ struct page *page)
+{
+ if (fscache_cookie_valid(cookie))
+ __fscache_uncache_page(cookie, page);
+}
+
+#endif /* _LINUX_FSCACHE_H */
Trond Myklebust <[email protected]> wrote:
> So why do you need a new address space operation? AFAICS the generic
> implementation will work for pretty much everyone who supports the
> existing prepare_write()/commit_write().
Because Christoph decreed that I wasn't allowed to call prepare_write() and
commit_write() directly. It's possible that the method should be in the
inode_operations rather than on the address space.
> Furthermore, you don't appear to supply any alternative "optimised"
> implementations...
Optimised in what fashion?
David
Trond Myklebust <[email protected]> wrote:
> > This is used by CacheFiles to detect read completion on a page in the
> > backing filesystem so that it can then copy the data to the waiting netfs
> > page.
>
> Won't it in any case want to lock the page too?
No. Why would it? All it wants to do is to read the page (copying it to the
netfs's page), assuming it becomes PG_uptodate.
> That would be the only way to ensure that the page is still mapped into the
> address space when you're writing it out...
I don't understand what you're getting at. Write the page out where? We've
just read it in from the cache, so why would we be writing it back out?
David
Casey Schaufler <[email protected]> wrote:
> They are nonetheless in effect and (heaven forbid) should they be
> abused you don't want to hide the facts from concerned observers.
Because, I suspect, what the observer through /proc should see is what the
process thinks it is doing, not what is transparently going on behind the
scenes.
David
Peter Staubach <[email protected]> wrote:
> Did I miss the section where the modified semantics about which
> mounted file systems can use the cache and which ones can not
> was implemented?
Yes.
David
David Howells <[email protected]> wrote:
> Peter Staubach <[email protected]> wrote:
>
> > Did I miss the section where the modified semantics about which
> > mounted file systems can use the cache and which ones can not
> > was implemented?
>
> Yes.
fs/nfs/super.c:
case Opt_sharecache:
mnt->flags &= ~NFS_MOUNT_UNSHARED;
break;
case Opt_nosharecache:
mnt->flags |= NFS_MOUNT_UNSHARED;
mnt->options &= ~NFS_OPTION_FSCACHE;
break;
case Opt_fscache:
/* sharing is mandatory with fscache */
mnt->options |= NFS_OPTION_FSCACHE;
mnt->flags &= ~NFS_MOUNT_UNSHARED;
break;
case Opt_nofscache:
mnt->options &= ~NFS_OPTION_FSCACHE;
break;
Hmmm... Actually, I'm not sure this is sufficient.
David
David Howells wrote:
> David Howells <[email protected]> wrote:
>
>
>> Peter Staubach <[email protected]> wrote:
>>
>>
>>> Did I miss the section where the modified semantics about which
>>> mounted file systems can use the cache and which ones can not
>>> was implemented?
>>>
>> Yes.
>>
>
> fs/nfs/super.c:
>
> case Opt_sharecache:
> mnt->flags &= ~NFS_MOUNT_UNSHARED;
> break;
> case Opt_nosharecache:
> mnt->flags |= NFS_MOUNT_UNSHARED;
> mnt->options &= ~NFS_OPTION_FSCACHE;
> break;
> case Opt_fscache:
> /* sharing is mandatory with fscache */
> mnt->options |= NFS_OPTION_FSCACHE;
> mnt->flags &= ~NFS_MOUNT_UNSHARED;
> break;
> case Opt_nofscache:
> mnt->options &= ~NFS_OPTION_FSCACHE;
> break;
>
> Hmmm... Actually, I'm not sure this is sufficient.
This doesn't seem to take into account any of the other options
which can cause sharing to be disabled. Perhaps SteveD can add
his patch to the mix which does resolve the issues?
Thanx...
ps
Hi, David,
One little thing I noticed:
> + * @cred_kernel_act_as:
> + * Set the credentials for a kernel service to act as (subjective context).
> + * @cred points to the credentials structure to be filled in.
> + * @service names the service making the request.
> + * @daemon: A userspace daemon to be used as a base for the context.
> + * @dentry: A file or dir to be used as a base for the file creation
> + * context.
> + * Return 0 if successful.
The comment describes a "dentry" argument, but the actual function does
not have that argument.
jon
Jonathan Corbet / LWN.net / [email protected]
Jonathan Corbet <[email protected]> wrote:
> The comment describes a "dentry" argument, but the actual function does
> not have that argument.
oops. Yeah, I decided I didn't want that there.
David