Commit 35773c93 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'afs-proc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

Pull AFS updates from Al Viro:
 "Assorted AFS stuff - ended up in vfs.git since most of that consists
  of David's AFS-related followups to Christoph's procfs series"

* 'afs-proc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  afs: Optimise callback breaking by not repeating volume lookup
  afs: Display manually added cells in dynamic root mount
  afs: Enable IPv6 DNS lookups
  afs: Show all of a server's addresses in /proc/fs/afs/servers
  afs: Handle CONFIG_PROC_FS=n
  proc: Make inline name size calculation automatic
  afs: Implement network namespacing
  afs: Mark afs_net::ws_cell as __rcu and set using rcu functions
  afs: Fix a Sparse warning in xdr_decode_AFSFetchStatus()
  proc: Add a way to make network proc files writable
  afs: Rearrange fs/afs/proc.c to remove remaining predeclarations.
  afs: Rearrange fs/afs/proc.c to move the show routines up
  afs: Rearrange fs/afs/proc.c by moving fops and open functions down
  afs: Move /proc management functions to the end of the file
parents 29d6849d 47ea0f2e
......@@ -5,7 +5,7 @@
afs-cache-$(CONFIG_AFS_FSCACHE) := cache.o
kafs-objs := \
kafs-y := \
$(afs-cache-y) \
addr_list.o \
callback.o \
......@@ -21,7 +21,6 @@ kafs-objs := \
main.o \
misc.o \
mntpt.o \
proc.o \
rotate.o \
rxrpc.o \
security.o \
......@@ -34,4 +33,5 @@ kafs-objs := \
write.o \
xattr.o
kafs-$(CONFIG_PROC_FS) += proc.o
obj-$(CONFIG_AFS_FS) := kafs.o
......@@ -215,7 +215,7 @@ struct afs_addr_list *afs_dns_query(struct afs_cell *cell, time64_t *_expiry)
_enter("%s", cell->name);
ret = dns_query("afsdb", cell->name, cell->name_len,
"ipv4", &vllist, _expiry);
"", &vllist, _expiry);
if (ret < 0)
return ERR_PTR(ret);
......
......@@ -20,6 +20,66 @@
#include <linux/sched.h>
#include "internal.h"
/*
* Create volume and callback interests on a server.
*/
static struct afs_cb_interest *afs_create_interest(struct afs_server *server,
struct afs_vnode *vnode)
{
struct afs_vol_interest *new_vi, *vi;
struct afs_cb_interest *new;
struct hlist_node **pp;
new_vi = kzalloc(sizeof(struct afs_vol_interest), GFP_KERNEL);
if (!new_vi)
return NULL;
new = kzalloc(sizeof(struct afs_cb_interest), GFP_KERNEL);
if (!new) {
kfree(new_vi);
return NULL;
}
new_vi->usage = 1;
new_vi->vid = vnode->volume->vid;
INIT_HLIST_NODE(&new_vi->srv_link);
INIT_HLIST_HEAD(&new_vi->cb_interests);
refcount_set(&new->usage, 1);
new->sb = vnode->vfs_inode.i_sb;
new->vid = vnode->volume->vid;
new->server = afs_get_server(server);
INIT_HLIST_NODE(&new->cb_vlink);
write_lock(&server->cb_break_lock);
for (pp = &server->cb_volumes.first; *pp; pp = &(*pp)->next) {
vi = hlist_entry(*pp, struct afs_vol_interest, srv_link);
if (vi->vid < new_vi->vid)
continue;
if (vi->vid > new_vi->vid)
break;
vi->usage++;
goto found_vi;
}
new_vi->srv_link.pprev = pp;
new_vi->srv_link.next = *pp;
if (*pp)
(*pp)->pprev = &new_vi->srv_link.next;
*pp = &new_vi->srv_link;
vi = new_vi;
new_vi = NULL;
found_vi:
new->vol_interest = vi;
hlist_add_head(&new->cb_vlink, &vi->cb_interests);
write_unlock(&server->cb_break_lock);
kfree(new_vi);
return new;
}
/*
* Set up an interest-in-callbacks record for a volume on a server and
* register it with the server.
......@@ -77,20 +137,10 @@ int afs_register_server_cb_interest(struct afs_vnode *vnode,
}
if (!cbi) {
new = kzalloc(sizeof(struct afs_cb_interest), GFP_KERNEL);
new = afs_create_interest(server, vnode);
if (!new)
return -ENOMEM;
refcount_set(&new->usage, 1);
new->sb = vnode->vfs_inode.i_sb;
new->vid = vnode->volume->vid;
new->server = afs_get_server(server);
INIT_LIST_HEAD(&new->cb_link);
write_lock(&server->cb_break_lock);
list_add_tail(&new->cb_link, &server->cb_interests);
write_unlock(&server->cb_break_lock);
write_lock(&slist->lock);
if (!entry->cb_interest) {
entry->cb_interest = afs_get_cb_interest(new);
......@@ -126,11 +176,22 @@ int afs_register_server_cb_interest(struct afs_vnode *vnode,
*/
void afs_put_cb_interest(struct afs_net *net, struct afs_cb_interest *cbi)
{
struct afs_vol_interest *vi;
if (cbi && refcount_dec_and_test(&cbi->usage)) {
if (!list_empty(&cbi->cb_link)) {
if (!hlist_unhashed(&cbi->cb_vlink)) {
write_lock(&cbi->server->cb_break_lock);
list_del_init(&cbi->cb_link);
hlist_del_init(&cbi->cb_vlink);
vi = cbi->vol_interest;
cbi->vol_interest = NULL;
if (--vi->usage == 0)
hlist_del(&vi->srv_link);
else
vi = NULL;
write_unlock(&cbi->server->cb_break_lock);
kfree(vi);
afs_put_server(net, cbi->server);
}
kfree(cbi);
......@@ -182,20 +243,34 @@ void afs_break_callback(struct afs_vnode *vnode)
static void afs_break_one_callback(struct afs_server *server,
struct afs_fid *fid)
{
struct afs_vol_interest *vi;
struct afs_cb_interest *cbi;
struct afs_iget_data data;
struct afs_vnode *vnode;
struct inode *inode;
read_lock(&server->cb_break_lock);
hlist_for_each_entry(vi, &server->cb_volumes, srv_link) {
if (vi->vid < fid->vid)
continue;
if (vi->vid > fid->vid) {
vi = NULL;
break;
}
//atomic_inc(&vi->usage);
break;
}
/* TODO: Find all matching volumes if we couldn't match the server and
* break them anyway.
*/
if (!vi)
goto out;
/* Step through all interested superblocks. There may be more than one
* because of cell aliasing.
*/
list_for_each_entry(cbi, &server->cb_interests, cb_link) {
if (cbi->vid != fid->vid)
continue;
hlist_for_each_entry(cbi, &vi->cb_interests, cb_vlink) {
if (fid->vnode == 0 && fid->unique == 0) {
/* The callback break applies to an entire volume. */
struct afs_super_info *as = AFS_FS_S(cbi->sb);
......@@ -217,6 +292,7 @@ static void afs_break_one_callback(struct afs_server *server,
}
}
out:
read_unlock(&server->cb_break_lock);
}
......
......@@ -15,6 +15,7 @@
#include <linux/dns_resolver.h>
#include <linux/sched.h>
#include <linux/inet.h>
#include <linux/namei.h>
#include <keys/rxrpc-type.h>
#include "internal.h"
......@@ -341,8 +342,8 @@ int afs_cell_init(struct afs_net *net, const char *rootcell)
/* install the new cell */
write_seqlock(&net->cells_lock);
old_root = net->ws_cell;
net->ws_cell = new_root;
old_root = rcu_access_pointer(net->ws_cell);
rcu_assign_pointer(net->ws_cell, new_root);
write_sequnlock(&net->cells_lock);
afs_put_cell(net, old_root);
......@@ -528,12 +529,14 @@ static int afs_activate_cell(struct afs_net *net, struct afs_cell *cell)
NULL, 0,
cell, 0, true);
#endif
ret = afs_proc_cell_setup(net, cell);
ret = afs_proc_cell_setup(cell);
if (ret < 0)
return ret;
spin_lock(&net->proc_cells_lock);
mutex_lock(&net->proc_cells_lock);
list_add_tail(&cell->proc_link, &net->proc_cells);
spin_unlock(&net->proc_cells_lock);
afs_dynroot_mkdir(net, cell);
mutex_unlock(&net->proc_cells_lock);
return 0;
}
......@@ -544,11 +547,12 @@ static void afs_deactivate_cell(struct afs_net *net, struct afs_cell *cell)
{
_enter("%s", cell->name);
afs_proc_cell_remove(net, cell);
afs_proc_cell_remove(cell);
spin_lock(&net->proc_cells_lock);
mutex_lock(&net->proc_cells_lock);
list_del_init(&cell->proc_link);
spin_unlock(&net->proc_cells_lock);
afs_dynroot_rmdir(net, cell);
mutex_unlock(&net->proc_cells_lock);
#ifdef CONFIG_AFS_FSCACHE
fscache_relinquish_cookie(cell->cache, NULL, false);
......@@ -755,8 +759,8 @@ void afs_cell_purge(struct afs_net *net)
_enter("");
write_seqlock(&net->cells_lock);
ws = net->ws_cell;
net->ws_cell = NULL;
ws = rcu_access_pointer(net->ws_cell);
RCU_INIT_POINTER(net->ws_cell, NULL);
write_sequnlock(&net->cells_lock);
afs_put_cell(net, ws);
......
......@@ -526,7 +526,7 @@ static void SRXAFSCB_TellMeAboutYourself(struct work_struct *work)
nifs = 0;
ifs = kcalloc(32, sizeof(*ifs), GFP_KERNEL);
if (ifs) {
nifs = afs_get_ipv4_interfaces(ifs, 32, false);
nifs = afs_get_ipv4_interfaces(call->net, ifs, 32, false);
if (nifs < 0) {
kfree(ifs);
ifs = NULL;
......
/* dir.c: AFS dynamic root handling
/* AFS dynamic root handling
*
* Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
......@@ -46,7 +46,7 @@ static int afs_probe_cell_name(struct dentry *dentry)
return 0;
}
ret = dns_query("afsdb", name, len, "ipv4", NULL, NULL);
ret = dns_query("afsdb", name, len, "", NULL, NULL);
if (ret == -ENODATA)
ret = -EDESTADDRREQ;
return ret;
......@@ -207,3 +207,125 @@ const struct dentry_operations afs_dynroot_dentry_operations = {
.d_release = afs_d_release,
.d_automount = afs_d_automount,
};
/*
* Create a manually added cell mount directory.
* - The caller must hold net->proc_cells_lock
*/
int afs_dynroot_mkdir(struct afs_net *net, struct afs_cell *cell)
{
struct super_block *sb = net->dynroot_sb;
struct dentry *root, *subdir;
int ret;
if (!sb || atomic_read(&sb->s_active) == 0)
return 0;
/* Let the ->lookup op do the creation */
root = sb->s_root;
inode_lock(root->d_inode);
subdir = lookup_one_len(cell->name, root, cell->name_len);
if (IS_ERR(subdir)) {
ret = PTR_ERR(subdir);
goto unlock;
}
/* Note that we're retaining an extra ref on the dentry */
subdir->d_fsdata = (void *)1UL;
ret = 0;
unlock:
inode_unlock(root->d_inode);
return ret;
}
/*
* Remove a manually added cell mount directory.
* - The caller must hold net->proc_cells_lock
*/
void afs_dynroot_rmdir(struct afs_net *net, struct afs_cell *cell)
{
struct super_block *sb = net->dynroot_sb;
struct dentry *root, *subdir;
if (!sb || atomic_read(&sb->s_active) == 0)
return;
root = sb->s_root;
inode_lock(root->d_inode);
/* Don't want to trigger a lookup call, which will re-add the cell */
subdir = try_lookup_one_len(cell->name, root, cell->name_len);
if (IS_ERR_OR_NULL(subdir)) {
_debug("lookup %ld", PTR_ERR(subdir));
goto no_dentry;
}
_debug("rmdir %pd %u", subdir, d_count(subdir));
if (subdir->d_fsdata) {
_debug("unpin %u", d_count(subdir));
subdir->d_fsdata = NULL;
dput(subdir);
}
dput(subdir);
no_dentry:
inode_unlock(root->d_inode);
_leave("");
}
/*
* Populate a newly created dynamic root with cell names.
*/
int afs_dynroot_populate(struct super_block *sb)
{
struct afs_cell *cell;
struct afs_net *net = afs_sb2net(sb);
int ret;
if (mutex_lock_interruptible(&net->proc_cells_lock) < 0)
return -ERESTARTSYS;
net->dynroot_sb = sb;
list_for_each_entry(cell, &net->proc_cells, proc_link) {
ret = afs_dynroot_mkdir(net, cell);
if (ret < 0)
goto error;
}
ret = 0;
out:
mutex_unlock(&net->proc_cells_lock);
return ret;
error:
net->dynroot_sb = NULL;
goto out;
}
/*
* When a dynamic root that's in the process of being destroyed, depopulate it
* of pinned directories.
*/
void afs_dynroot_depopulate(struct super_block *sb)
{
struct afs_net *net = afs_sb2net(sb);
struct dentry *root = sb->s_root, *subdir, *tmp;
/* Prevent more subdirs from being created */
mutex_lock(&net->proc_cells_lock);
if (net->dynroot_sb == sb)
net->dynroot_sb = NULL;
mutex_unlock(&net->proc_cells_lock);
inode_lock(root->d_inode);
/* Remove all the pins for dirs created for manually added cells */
list_for_each_entry_safe(subdir, tmp, &root->d_subdirs, d_child) {
if (subdir->d_fsdata) {
subdir->d_fsdata = NULL;
dput(subdir);
}
}
inode_unlock(root->d_inode);
}
......@@ -138,10 +138,6 @@ static int xdr_decode_AFSFetchStatus(struct afs_call *call,
u64 data_version, size;
u32 type, abort_code;
u8 flags = 0;
int ret;
if (vnode)
write_seqlock(&vnode->cb_lock);
abort_code = ntohl(xdr->abort_code);
......@@ -154,8 +150,7 @@ static int xdr_decode_AFSFetchStatus(struct afs_call *call,
* case.
*/
status->abort_code = abort_code;
ret = 0;
goto out;
return 0;
}
pr_warn("Unknown AFSFetchStatus version %u\n", ntohl(xdr->if_version));
......@@ -164,8 +159,7 @@ static int xdr_decode_AFSFetchStatus(struct afs_call *call,
if (abort_code != 0 && inline_error) {
status->abort_code = abort_code;
ret = 0;
goto out;
return 0;
}
type = ntohl(xdr->type);
......@@ -235,17 +229,35 @@ static int xdr_decode_AFSFetchStatus(struct afs_call *call,
flags);
}
ret = 0;
out:
if (vnode)
write_sequnlock(&vnode->cb_lock);
return ret;
return 0;
bad:
xdr_dump_bad(*_bp);
ret = afs_protocol_error(call, -EBADMSG);
goto out;
return afs_protocol_error(call, -EBADMSG);
}
/*
* Decode the file status. We need to lock the target vnode if we're going to
* update its status so that stat() sees the attributes update atomically.
*/
static int afs_decode_status(struct afs_call *call,
const __be32 **_bp,
struct afs_file_status *status,
struct afs_vnode *vnode,
const afs_dataversion_t *expected_version,
struct afs_read *read_req)
{
int ret;
if (!vnode)
return xdr_decode_AFSFetchStatus(call, _bp, status, vnode,
expected_version, read_req);
write_seqlock(&vnode->cb_lock);
ret = xdr_decode_AFSFetchStatus(call, _bp, status, vnode,
expected_version, read_req);
write_sequnlock(&vnode->cb_lock);
return ret;
}
/*
......@@ -387,7 +399,7 @@ static int afs_deliver_fs_fetch_status_vnode(struct afs_call *call)
/* unmarshall the reply once we've received all of it */
bp = call->buffer;
if (xdr_decode_AFSFetchStatus(call, &bp, &vnode->status, vnode,
if (afs_decode_status(call, &bp, &vnode->status, vnode,
&call->expected_version, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
xdr_decode_AFSCallBack(call, vnode, &bp);
......@@ -568,7 +580,7 @@ static int afs_deliver_fs_fetch_data(struct afs_call *call)
return ret;
bp = call->buffer;
if (xdr_decode_AFSFetchStatus(call, &bp, &vnode->status, vnode,
if (afs_decode_status(call, &bp, &vnode->status, vnode,
&vnode->status.data_version, req) < 0)
return afs_protocol_error(call, -EBADMSG);
xdr_decode_AFSCallBack(call, vnode, &bp);
......@@ -721,8 +733,8 @@ static int afs_deliver_fs_create_vnode(struct afs_call *call)
/* unmarshall the reply once we've received all of it */
bp = call->buffer;
xdr_decode_AFSFid(&bp, call->reply[1]);
if (xdr_decode_AFSFetchStatus(call, &bp, call->reply[2], NULL, NULL, NULL) < 0 ||
xdr_decode_AFSFetchStatus(call, &bp, &vnode->status, vnode,
if (afs_decode_status(call, &bp, call->reply[2], NULL, NULL, NULL) < 0 ||
afs_decode_status(call, &bp, &vnode->status, vnode,
&call->expected_version, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
xdr_decode_AFSCallBack_raw(&bp, call->reply[3]);
......@@ -827,7 +839,7 @@ static int afs_deliver_fs_remove(struct afs_call *call)
/* unmarshall the reply once we've received all of it */
bp = call->buffer;
if (xdr_decode_AFSFetchStatus(call, &bp, &vnode->status, vnode,
if (afs_decode_status(call, &bp, &vnode->status, vnode,
&call->expected_version, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
/* xdr_decode_AFSVolSync(&bp, call->reply[X]); */
......@@ -917,8 +929,8 @@ static int afs_deliver_fs_link(struct afs_call *call)
/* unmarshall the reply once we've received all of it */
bp = call->buffer;
if (xdr_decode_AFSFetchStatus(call, &bp, &vnode->status, vnode, NULL, NULL) < 0 ||
xdr_decode_AFSFetchStatus(call, &bp, &dvnode->status, dvnode,
if (afs_decode_status(call, &bp, &vnode->status, vnode, NULL, NULL) < 0 ||
afs_decode_status(call, &bp, &dvnode->status, dvnode,
&call->expected_version, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
/* xdr_decode_AFSVolSync(&bp, call->reply[X]); */
......@@ -1004,8 +1016,8 @@ static int afs_deliver_fs_symlink(struct afs_call *call)
/* unmarshall the reply once we've received all of it */
bp = call->buffer;
xdr_decode_AFSFid(&bp, call->reply[1]);
if (xdr_decode_AFSFetchStatus(call, &bp, call->reply[2], NULL, NULL, NULL) ||
xdr_decode_AFSFetchStatus(call, &bp, &vnode->status, vnode,
if (afs_decode_status(call, &bp, call->reply[2], NULL, NULL, NULL) ||
afs_decode_status(call, &bp, &vnode->status, vnode,
&call->expected_version, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
/* xdr_decode_AFSVolSync(&bp, call->reply[X]); */
......@@ -1110,11 +1122,11 @@ static int afs_deliver_fs_rename(struct afs_call *call)
/* unmarshall the reply once we've received all of it */
bp = call->buffer;
if (xdr_decode_AFSFetchStatus(call, &bp, &orig_dvnode->status, orig_dvnode,
if (afs_decode_status(call, &bp, &orig_dvnode->status, orig_dvnode,
&call->expected_version, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
if (new_dvnode != orig_dvnode &&
xdr_decode_AFSFetchStatus(call, &bp, &new_dvnode->status, new_dvnode,
afs_decode_status(call, &bp, &new_dvnode->status, new_dvnode,
&call->expected_version_2, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
/* xdr_decode_AFSVolSync(&bp, call->reply[X]); */
......@@ -1219,7 +1231,7 @@ static int afs_deliver_fs_store_data(struct afs_call *call)
/* unmarshall the reply once we've received all of it */
bp = call->buffer;
if (xdr_decode_AFSFetchStatus(call, &bp, &vnode->status, vnode,
if (afs_decode_status(call, &bp, &vnode->status, vnode,
&call->expected_version, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
/* xdr_decode_AFSVolSync(&bp, call->reply[X]); */
......@@ -1395,7 +1407,7 @@ static int afs_deliver_fs_store_status(struct afs_call *call)
/* unmarshall the reply once we've received all of it */
bp = call->buffer;
if (xdr_decode_AFSFetchStatus(call, &bp, &vnode->status, vnode,
if (afs_decode_status(call, &bp, &vnode->status, vnode,
&call->expected_version, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
/* xdr_decode_AFSVolSync(&bp, call->reply[X]); */
......@@ -2097,7 +2109,7 @@ static int afs_deliver_fs_fetch_status(struct afs_call *call)
/* unmarshall the reply once we've received all of it */
bp = call->buffer;
xdr_decode_AFSFetchStatus(call, &bp, status, vnode,
afs_decode_status(call, &bp, status, vnode,
&call->expected_version, NULL);
callback[call->count].version = ntohl(bp[0]);
callback[call->count].expiry = ntohl(bp[1]);
......@@ -2209,7 +2221,7 @@ static int afs_deliver_fs_inline_bulk_status(struct afs_call *call)
bp = call->buffer;
statuses = call->reply[1];
if (xdr_decode_AFSFetchStatus(call, &bp, &statuses[call->count],
if (afs_decode_status(call, &bp, &statuses[call->count],
call->count == 0 ? vnode : NULL,
NULL, NULL) < 0)
return afs_protocol_error(call, -EBADMSG);
......
......@@ -22,6 +22,8 @@
#include <linux/backing-dev.h>
#include <linux/uuid.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
#include <net/sock.h>
#include <net/af_rxrpc.h>
#include "afs.h"
......@@ -40,7 +42,8 @@ struct afs_mount_params {
afs_voltype_t type; /* type of volume requested */
int volnamesz; /* size of volume name */
const char *volname; /* name of volume to mount */
struct afs_net *net; /* Network namespace in effect */
struct net *net_ns; /* Network namespace in effect */
struct afs_net *net; /* the AFS net namespace stuff */
struct afs_cell *cell; /* cell in which to find volume */
struct afs_volume *volume; /* volume record */
struct key *key; /* key to use for secure mounting */
......@@ -189,7 +192,7 @@ struct afs_read {
* - there's one superblock per volume
*/
struct afs_super_info {
struct afs_net *net; /* Network namespace */
struct net *net_ns; /* Network namespace */
struct afs_cell *cell; /* The cell in which the volume resides */
struct afs_volume *volume; /* volume record */
bool dyn_root; /* True if dynamic root */
......@@ -210,7 +213,6 @@ struct afs_sysnames {
char *subs[AFS_NR_SYSNAME];
refcount_t usage;
unsigned short nr;
short error;
char blank[1];
};
......@@ -218,6 +220,7 @@ struct afs_sysnames {
* AFS network namespace record.
*/
struct afs_net {
struct net *net; /* Backpointer to the owning net namespace */
struct afs_uuid uuid;
bool live; /* F if this namespace is being removed */
......@@ -231,13 +234,13 @@ struct afs_net {
/* Cell database */
struct rb_root cells;
struct afs_cell *ws_cell;
struct afs_cell __rcu *ws_cell;
struct work_struct cells_manager;
struct timer_list cells_timer;
atomic_t cells_outstanding;
seqlock_t cells_lock;
spinlock_t proc_cells_lock;
struct mutex proc_cells_lock;
struct list_head proc_cells;
/* Known servers. Theoretically each fileserver can only be in one
......@@ -261,6 +264,7 @@ struct afs_net {
struct mutex lock_manager_mutex;
/* Misc */
struct super_block *dynroot_sb; /* Dynamic root mount superblock */
struct proc_dir_entry *proc_afs; /* /proc/net/afs directory */
struct afs_sysnames *sysnames;
rwlock_t sysnames_lock;
......@@ -280,7 +284,6 @@ struct afs_net {
};
extern const char afs_init_sysname[];
extern struct afs_net __afs_net;// Dummy AFS network namespace; TODO: replace with real netns
enum afs_cell_state {
AFS_CELL_UNSET,
......@@ -404,16 +407,27 @@ struct afs_server {
rwlock_t fs_lock; /* access lock */
/* callback promise management */
struct list_head cb_interests; /* List of superblocks using this server */
struct hlist_head cb_volumes; /* List of volume interests on this server */
unsigned cb_s_break; /* Break-everything counter. */
rwlock_t cb_break_lock; /* Volume finding lock */
};
/*
* Volume collation in the server's callback interest list.
*/
struct afs_vol_interest {
struct hlist_node srv_link; /* Link in server->cb_volumes */
struct hlist_head cb_interests; /* List of callback interests on the server */
afs_volid_t vid; /* Volume ID to match */
unsigned int usage;
};
/*
* Interest by a superblock on a server.
*/
struct afs_cb_interest {
struct list_head cb_link; /* Link in server->cb_interests */
struct hlist_node cb_vlink; /* Link in vol_interest->cb_interests */
struct afs_vol_interest *vol_interest;
struct afs_server *server; /* Server on which this interest resides */
struct super_block *sb; /* Superblock on which inodes reside */
afs_volid_t vid; /* Volume ID to match */
......@@ -720,6 +734,10 @@ extern const struct inode_operations afs_dynroot_inode_operations;
extern const struct dentry_operations afs_dynroot_dentry_operations;
extern struct inode *afs_try_auto_mntpt(struct dentry *, struct inode *);
extern int afs_dynroot_mkdir(struct afs_net *, struct afs_cell *);
extern void afs_dynroot_rmdir(struct afs_net *, struct afs_cell *);
extern int afs_dynroot_populate(struct super_block *);
extern void afs_dynroot_depopulate(struct super_block *);
/*
* file.c
......@@ -806,34 +824,36 @@ extern int afs_drop_inode(struct inode *);
* main.c
*/
extern struct workqueue_struct *afs_wq;
extern int afs_net_id;
static inline struct afs_net *afs_d2net(struct dentry *dentry)
static inline struct afs_net *afs_net(struct net *net)
{
return &__afs_net;
return net_generic(net, afs_net_id);
}
static inline struct afs_net *afs_i2net(struct inode *inode)
static inline struct afs_net *afs_sb2net(struct super_block *sb)
{
return &__afs_net;
return afs_net(AFS_FS_S(sb)->net_ns);
}
static inline struct afs_net *afs_v2net(struct afs_vnode *vnode)
static inline struct afs_net *afs_d2net(struct dentry *dentry)
{
return &__afs_net;
return afs_sb2net(dentry->d_sb);
}
static inline struct afs_net *afs_sock2net(struct sock *sk)
static inline struct afs_net *afs_i2net(struct inode *inode)
{
return &__afs_net;
return afs_sb2net(inode->i_sb);
}
static inline struct afs_net *afs_get_net(struct afs_net *net)
static inline struct afs_net *afs_v2net(struct afs_vnode *vnode)
{
return net;
return afs_i2net(&vnode->vfs_inode);
}
static inline void afs_put_net(struct afs_net *net)
static inline struct afs_net *afs_sock2net(struct sock *sk)
{
return net_generic(sock_net(sk), afs_net_id);
}
static inline void __afs_stat(atomic_t *s)
......@@ -861,16 +881,25 @@ extern void afs_mntpt_kill_timer(void);
/*
* netdevices.c
*/
extern int afs_get_ipv4_interfaces(struct afs_interface *, size_t, bool);
extern int afs_get_ipv4_interfaces(struct afs_net *, struct afs_interface *,
size_t, bool);
/*
* proc.c
*/
#ifdef CONFIG_PROC_FS
extern int __net_init afs_proc_init(struct afs_net *);
extern void __net_exit afs_proc_cleanup(struct afs_net *);
extern int afs_proc_cell_setup(struct afs_net *, struct afs_cell *);
extern void afs_proc_cell_remove(struct afs_net *, struct afs_cell *);
extern int afs_proc_cell_setup(struct afs_cell *);
extern void afs_proc_cell_remove(struct afs_cell *);
extern void afs_put_sysnames(struct afs_sysnames *);
#else
static inline int afs_proc_init(struct afs_net *net) { return 0; }
static inline void afs_proc_cleanup(struct afs_net *net) {}
static inline int afs_proc_cell_setup(struct afs_cell *cell) { return 0; }
static inline void afs_proc_cell_remove(struct afs_cell *cell) {}
static inline void afs_put_sysnames(struct afs_sysnames *sysnames) {}
#endif
/*
* rotate.c
......@@ -1002,7 +1031,7 @@ extern bool afs_annotate_server_list(struct afs_server_list *, struct afs_server
* super.c
*/
extern int __init afs_fs_init(void);
extern void __exit afs_fs_exit(void);
extern void afs_fs_exit(void);
/*
* vlclient.c
......
......@@ -15,6 +15,7 @@
#include <linux/completion.h>
#include <linux/sched.h>
#include <linux/random.h>
#include <linux/proc_fs.h>
#define CREATE_TRACE_POINTS
#include "internal.h"
......@@ -32,7 +33,7 @@ module_param(rootcell, charp, 0);
MODULE_PARM_DESC(rootcell, "root AFS cell name and VL server IP addr list");
struct workqueue_struct *afs_wq;
struct afs_net __afs_net;
static struct proc_dir_entry *afs_proc_symlink;
#if defined(CONFIG_ALPHA)
const char afs_init_sysname[] = "alpha_linux26";
......@@ -67,11 +68,13 @@ const char afs_init_sysname[] = "unknown_linux26";
/*
* Initialise an AFS network namespace record.
*/
static int __net_init afs_net_init(struct afs_net *net)
static int __net_init afs_net_init(struct net *net_ns)
{
struct afs_sysnames *sysnames;
struct afs_net *net = afs_net(net_ns);
int ret;
net->net = net_ns;
net->live = true;
generate_random_uuid((unsigned char *)&net->uuid);
......@@ -83,7 +86,7 @@ static int __net_init afs_net_init(struct afs_net *net)
INIT_WORK(&net->cells_manager, afs_manage_cells);
timer_setup(&net->cells_timer, afs_cells_timer, 0);
spin_lock_init(&net->proc_cells_lock);
mutex_init(&net->proc_cells_lock);
INIT_LIST_HEAD(&net->proc_cells);
seqlock_init(&net->fs_lock);
......@@ -142,8 +145,10 @@ static int __net_init afs_net_init(struct afs_net *net)
/*
* Clean up and destroy an AFS network namespace record.
*/
static void __net_exit afs_net_exit(struct afs_net *net)
static void __net_exit afs_net_exit(struct net *net_ns)
{
struct afs_net *net = afs_net(net_ns);
net->live = false;
afs_cell_purge(net);
afs_purge_servers(net);
......@@ -152,6 +157,13 @@ static void __net_exit afs_net_exit(struct afs_net *net)
afs_put_sysnames(net->sysnames);
}
static struct pernet_operations afs_net_ops = {
.init = afs_net_init,
.exit = afs_net_exit,
.id = &afs_net_id,
.size = sizeof(struct afs_net),
};
/*
* initialise the AFS client FS module
*/
......@@ -178,7 +190,7 @@ static int __init afs_init(void)
goto error_cache;
#endif
ret = afs_net_init(&__afs_net);
ret = register_pernet_subsys(&afs_net_ops);
if (ret < 0)
goto error_net;
......@@ -187,10 +199,18 @@ static int __init afs_init(void)
if (ret < 0)
goto error_fs;
afs_proc_symlink = proc_symlink("fs/afs", NULL, "../self/net/afs");
if (IS_ERR(afs_proc_symlink)) {
ret = PTR_ERR(afs_proc_symlink);
goto error_proc;
}
return ret;
error_proc:
afs_fs_exit();
error_fs:
afs_net_exit(&__afs_net);
unregister_pernet_subsys(&afs_net_ops);
error_net:
#ifdef CONFIG_AFS_FSCACHE
fscache_unregister_netfs(&afs_cache_netfs);
......@@ -219,8 +239,9 @@ static void __exit afs_exit(void)
{
printk(KERN_INFO "kAFS: Red Hat AFS client v0.1 unregistering.\n");
proc_remove(afs_proc_symlink);
afs_fs_exit();
afs_net_exit(&__afs_net);
unregister_pernet_subsys(&afs_net_ops);
#ifdef CONFIG_AFS_FSCACHE
fscache_unregister_netfs(&afs_cache_netfs);
#endif
......
......@@ -17,8 +17,8 @@
* - maxbufs must be at least 1
* - returns the number of interface records in the buffer
*/
int afs_get_ipv4_interfaces(struct afs_interface *bufs, size_t maxbufs,
bool wantloopback)
int afs_get_ipv4_interfaces(struct afs_net *net, struct afs_interface *bufs,
size_t maxbufs, bool wantloopback)
{
struct net_device *dev;
struct in_device *idev;
......@@ -27,7 +27,7 @@ int afs_get_ipv4_interfaces(struct afs_interface *bufs, size_t maxbufs,
ASSERT(maxbufs > 0);
rtnl_lock();
for_each_netdev(&init_net, dev) {
for_each_netdev(net->net, dev) {
if (dev->type == ARPHRD_LOOPBACK && !wantloopback)
continue;
idev = __in_dev_get_rtnl(dev);
......
This diff is collapsed.
......@@ -46,7 +46,7 @@ int afs_open_socket(struct afs_net *net)
_enter("");
ret = sock_create_kern(&init_net, AF_RXRPC, SOCK_DGRAM, PF_INET6, &socket);
ret = sock_create_kern(net->net, AF_RXRPC, SOCK_DGRAM, PF_INET6, &socket);
if (ret < 0)
goto error_1;
......
......@@ -228,7 +228,7 @@ static struct afs_server *afs_alloc_server(struct afs_net *net,
server->flags = (1UL << AFS_SERVER_FL_NEW);
server->update_at = ktime_get_real_seconds() + afs_server_update_delay;
rwlock_init(&server->fs_lock);
INIT_LIST_HEAD(&server->cb_interests);
INIT_HLIST_HEAD(&server->cb_volumes);
rwlock_init(&server->cb_break_lock);
afs_inc_servers_outstanding(net);
......
......@@ -48,6 +48,8 @@ struct file_system_type afs_fs_type = {
};
MODULE_ALIAS_FS("afs");
int afs_net_id;
static const struct super_operations afs_super_ops = {
.statfs = afs_statfs,
.alloc_inode = afs_alloc_inode,
......@@ -117,7 +119,7 @@ int __init afs_fs_init(void)
/*
* clean up the filesystem
*/
void __exit afs_fs_exit(void)
void afs_fs_exit(void)
{
_enter("");
......@@ -351,14 +353,19 @@ static int afs_test_super(struct super_block *sb, void *data)
struct afs_super_info *as1 = data;
struct afs_super_info *as = AFS_FS_S(sb);
return (as->net == as1->net &&
return (as->net_ns == as1->net_ns &&
as->volume &&
as->volume->vid == as1->volume->vid);
as->volume->vid == as1->volume->vid &&
!as->dyn_root);
}
static int afs_dynroot_test_super(struct super_block *sb, void *data)
{
return false;
struct afs_super_info *as1 = data;
struct afs_super_info *as = AFS_FS_S(sb);
return (as->net_ns == as1->net_ns &&
as->dyn_root);
}
static int afs_set_super(struct super_block *sb, void *data)
......@@ -418,10 +425,14 @@ static int afs_fill_super(struct super_block *sb,
if (!sb->s_root)
goto error;
if (params->dyn_root)
if (as->dyn_root) {
sb->s_d_op = &afs_dynroot_dentry_operations;
else
ret = afs_dynroot_populate(sb);
if (ret < 0)
goto error;
} else {
sb->s_d_op = &afs_fs_dentry_operations;
}
_leave(" = 0");
return 0;
......@@ -437,7 +448,7 @@ static struct afs_super_info *afs_alloc_sbi(struct afs_mount_params *params)
as = kzalloc(sizeof(struct afs_super_info), GFP_KERNEL);
if (as) {
as->net = afs_get_net(params->net);
as->net_ns = get_net(params->net_ns);
if (params->dyn_root)
as->dyn_root = true;
else
......@@ -450,12 +461,31 @@ static void afs_destroy_sbi(struct afs_super_info *as)
{
if (as) {
afs_put_volume(as->cell, as->volume);
afs_put_cell(as->net, as->cell);
afs_put_net(as->net);
afs_put_cell(afs_net(as->net_ns), as->cell);
put_net(as->net_ns);
kfree(as);
}
}
static void afs_kill_super(struct super_block *sb)
{
struct afs_super_info *as = AFS_FS_S(sb);
struct afs_net *net = afs_net(as->net_ns);
if (as->dyn_root)
afs_dynroot_depopulate(sb);
/* Clear the callback interests (which will do ilookup5) before
* deactivating the superblock.
*/
if (as->volume)
afs_clear_callback_interests(net, as->volume->servers);
kill_anon_super(sb);
if (as->volume)
afs_deactivate_volume(as->volume);
afs_destroy_sbi(as);
}
/*
* get an AFS superblock
*/
......@@ -472,11 +502,12 @@ static struct dentry *afs_mount(struct file_system_type *fs_type,
_enter(",,%s,%p", dev_name, options);
memset(&params, 0, sizeof(params));
params.net = &__afs_net;
ret = -EINVAL;
if (current->nsproxy->net_ns != &init_net)
goto error;
params.net_ns = current->nsproxy->net_ns;
params.net = afs_net(params.net_ns);
/* parse the options and device name */
if (options) {
......@@ -563,21 +594,6 @@ static struct dentry *afs_mount(struct file_system_type *fs_type,
return ERR_PTR(ret);
}
static void afs_kill_super(struct super_block *sb)
{
struct afs_super_info *as = AFS_FS_S(sb);
/* Clear the callback interests (which will do ilookup5) before
* deactivating the superblock.
*/
if (as->volume)
afs_clear_callback_interests(as->net, as->volume->servers);
kill_anon_super(sb);
if (as->volume)
afs_deactivate_volume(as->volume);
afs_destroy_sbi(as);
}
/*
* Initialise an inode cache slab element prior to any use. Note that
* afs_alloc_inode() *must* reset anything that could incorrectly leak from one
......
......@@ -2463,6 +2463,35 @@ static int lookup_one_len_common(const char *name, struct dentry *base,
return inode_permission(base->d_inode, MAY_EXEC);
}
/**
* try_lookup_one_len - filesystem helper to lookup single pathname component
* @name: pathname component to lookup
* @base: base directory to lookup from
* @len: maximum length @len should be interpreted to
*
* Look up a dentry by name in the dcache, returning NULL if it does not
* currently exist. The function does not try to create a dentry.
*
* Note that this routine is purely a helper for filesystem usage and should
* not be called by generic code.
*
* The caller must hold base->i_mutex.
*/
struct dentry *try_lookup_one_len(const char *name, struct dentry *base, int len)
{
struct qstr this;
int err;
WARN_ON_ONCE(!inode_is_locked(base->d_inode));
err = lookup_one_len_common(name, base, len, &this);
if (err)
return ERR_PTR(err);
return lookup_dcache(&this, base, 0);
}
EXPORT_SYMBOL(try_lookup_one_len);
/**
* lookup_one_len - filesystem helper to lookup single pathname component
* @name: pathname component to lookup
......
......@@ -409,7 +409,7 @@ static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent,
if (!ent)
goto out;
if (qstr.len + 1 <= sizeof(ent->inline_name)) {
if (qstr.len + 1 <= SIZEOF_PDE_INLINE_NAME) {
ent->name = ent->inline_name;
} else {
ent->name = kmalloc(qstr.len + 1, GFP_KERNEL);
......@@ -740,3 +740,27 @@ void *PDE_DATA(const struct inode *inode)
return __PDE_DATA(inode);
}
EXPORT_SYMBOL(PDE_DATA);
/*
* Pull a user buffer into memory and pass it to the file's write handler if
* one is supplied. The ->write() method is permitted to modify the
* kernel-side buffer.
*/
ssize_t proc_simple_write(struct file *f, const char __user *ubuf, size_t size,
loff_t *_pos)
{
struct proc_dir_entry *pde = PDE(file_inode(f));
char *buf;
int ret;
if (!pde->write)
return -EACCES;
if (size == 0 || size > PAGE_SIZE - 1)
return -EINVAL;
buf = memdup_user_nul(ubuf, size);
if (IS_ERR(buf))
return PTR_ERR(buf);
ret = pde->write(f, buf, size);
kfree(buf);
return ret == 0 ? size : ret;
}
......@@ -105,9 +105,8 @@ void __init proc_init_kmemcache(void)
kmem_cache_create("pde_opener", sizeof(struct pde_opener), 0,
SLAB_ACCOUNT|SLAB_PANIC, NULL);
proc_dir_entry_cache = kmem_cache_create_usercopy(
"proc_dir_entry", sizeof(struct proc_dir_entry), 0, SLAB_PANIC,
offsetof(struct proc_dir_entry, inline_name),
sizeof_field(struct proc_dir_entry, inline_name), NULL);
"proc_dir_entry", SIZEOF_PDE_SLOT, 0, SLAB_PANIC,
OFFSETOF_PDE_NAME, SIZEOF_PDE_INLINE_NAME, NULL);
}
static int proc_show_options(struct seq_file *seq, struct dentry *root)
......
......@@ -48,6 +48,7 @@ struct proc_dir_entry {
const struct seq_operations *seq_ops;
int (*single_show)(struct seq_file *, void *);
};
proc_write_t write;
void *data;
unsigned int state_size;
unsigned int low_ino;
......@@ -61,14 +62,20 @@ struct proc_dir_entry {
char *name;
umode_t mode;
u8 namelen;
#ifdef CONFIG_64BIT
#define SIZEOF_PDE_INLINE_NAME (192-155)
#else
#define SIZEOF_PDE_INLINE_NAME (128-95)
#endif
char inline_name[SIZEOF_PDE_INLINE_NAME];
char inline_name[];
} __randomize_layout;
#define OFFSETOF_PDE_NAME offsetof(struct proc_dir_entry, inline_name)
#define SIZEOF_PDE_SLOT \
(OFFSETOF_PDE_NAME + 34 <= 64 ? 64 : \
OFFSETOF_PDE_NAME + 34 <= 128 ? 128 : \
OFFSETOF_PDE_NAME + 34 <= 192 ? 192 : \
OFFSETOF_PDE_NAME + 34 <= 256 ? 256 : \
OFFSETOF_PDE_NAME + 34 <= 512 ? 512 : \
0)
#define SIZEOF_PDE_INLINE_NAME (SIZEOF_PDE_SLOT - OFFSETOF_PDE_NAME)
extern struct kmem_cache *proc_dir_entry_cache;
void pde_free(struct proc_dir_entry *pde);
......@@ -189,6 +196,7 @@ static inline bool is_empty_pde(const struct proc_dir_entry *pde)
{
return S_ISDIR(pde->mode) && !pde->proc_iops;
}
extern ssize_t proc_simple_write(struct file *, const char __user *, size_t, loff_t *);
/*
* inode.c
......
......@@ -46,6 +46,9 @@ static int seq_open_net(struct inode *inode, struct file *file)
WARN_ON_ONCE(state_size < sizeof(*p));
if (file->f_mode & FMODE_WRITE && !PDE(inode)->write)
return -EACCES;
net = get_proc_net(inode);
if (!net)
return -ENXIO;
......@@ -73,6 +76,7 @@ static int seq_release_net(struct inode *ino, struct file *f)
static const struct file_operations proc_net_seq_fops = {
.open = seq_open_net,
.read = seq_read,
.write = proc_simple_write,
.llseek = seq_lseek,
.release = seq_release_net,
};
......@@ -93,6 +97,50 @@ struct proc_dir_entry *proc_create_net_data(const char *name, umode_t mode,
}
EXPORT_SYMBOL_GPL(proc_create_net_data);
/**
* proc_create_net_data_write - Create a writable net_ns-specific proc file
* @name: The name of the file.
* @mode: The file's access mode.
* @parent: The parent directory in which to create.
* @ops: The seq_file ops with which to read the file.
* @write: The write method which which to 'modify' the file.
* @data: Data for retrieval by PDE_DATA().
*
* Create a network namespaced proc file in the @parent directory with the
* specified @name and @mode that allows reading of a file that displays a
* series of elements and also provides for the file accepting writes that have
* some arbitrary effect.
*
* The functions in the @ops table are used to iterate over items to be
* presented and extract the readable content using the seq_file interface.
*
* The @write function is called with the data copied into a kernel space
* scratch buffer and has a NUL appended for convenience. The buffer may be
* modified by the @write function. @write should return 0 on success.
*
* The @data value is accessible from the @show and @write functions by calling
* PDE_DATA() on the file inode. The network namespace must be accessed by
* calling seq_file_net() on the seq_file struct.
*/
struct proc_dir_entry *proc_create_net_data_write(const char *name, umode_t mode,
struct proc_dir_entry *parent,
const struct seq_operations *ops,
proc_write_t write,
unsigned int state_size, void *data)
{
struct proc_dir_entry *p;
p = proc_create_reg(name, mode, &parent, data);
if (!p)
return NULL;
p->proc_fops = &proc_net_seq_fops;
p->seq_ops = ops;
p->state_size = state_size;
p->write = write;
return proc_register(parent, p);
}
EXPORT_SYMBOL_GPL(proc_create_net_data_write);
static int single_open_net(struct inode *inode, struct file *file)
{
struct proc_dir_entry *de = PDE(inode);
......@@ -119,6 +167,7 @@ static int single_release_net(struct inode *ino, struct file *f)
static const struct file_operations proc_net_single_fops = {
.open = single_open_net,
.read = seq_read,
.write = proc_simple_write,
.llseek = seq_lseek,
.release = single_release_net,
};
......@@ -138,6 +187,49 @@ struct proc_dir_entry *proc_create_net_single(const char *name, umode_t mode,
}
EXPORT_SYMBOL_GPL(proc_create_net_single);
/**
* proc_create_net_single_write - Create a writable net_ns-specific proc file
* @name: The name of the file.
* @mode: The file's access mode.
* @parent: The parent directory in which to create.
* @show: The seqfile show method with which to read the file.
* @write: The write method which which to 'modify' the file.
* @data: Data for retrieval by PDE_DATA().
*
* Create a network-namespaced proc file in the @parent directory with the
* specified @name and @mode that allows reading of a file that displays a
* single element rather than a series and also provides for the file accepting
* writes that have some arbitrary effect.
*
* The @show function is called to extract the readable content via the
* seq_file interface.
*
* The @write function is called with the data copied into a kernel space
* scratch buffer and has a NUL appended for convenience. The buffer may be
* modified by the @write function. @write should return 0 on success.
*
* The @data value is accessible from the @show and @write functions by calling
* PDE_DATA() on the file inode. The network namespace must be accessed by
* calling seq_file_single_net() on the seq_file struct.
*/
struct proc_dir_entry *proc_create_net_single_write(const char *name, umode_t mode,
struct proc_dir_entry *parent,
int (*show)(struct seq_file *, void *),
proc_write_t write,
void *data)
{
struct proc_dir_entry *p;
p = proc_create_reg(name, mode, &parent, data);
if (!p)
return NULL;
p->proc_fops = &proc_net_single_fops;
p->single_show = show;
p->write = write;
return proc_register(parent, p);
}
EXPORT_SYMBOL_GPL(proc_create_net_single_write);
static struct net *get_proc_task_net(struct inode *dir)
{
struct task_struct *task;
......
......@@ -204,8 +204,7 @@ struct proc_dir_entry proc_root = {
.proc_fops = &proc_root_operations,
.parent = &proc_root,
.subdir = RB_ROOT,
.name = proc_root.inline_name,
.inline_name = "/proc",
.name = "/proc",
};
int pid_ns_prepare_proc(struct pid_namespace *ns)
......
......@@ -81,6 +81,7 @@ extern void done_path_create(struct path *, struct dentry *);
extern struct dentry *kern_path_locked(const char *, struct path *);
extern int kern_path_mountpoint(int, const char *, struct path *, unsigned int);
extern struct dentry *try_lookup_one_len(const char *, struct dentry *, int);
extern struct dentry *lookup_one_len(const char *, struct dentry *, int);
extern struct dentry *lookup_one_len_unlocked(const char *, struct dentry *, int);
......
......@@ -14,6 +14,8 @@ struct seq_operations;
#ifdef CONFIG_PROC_FS
typedef int (*proc_write_t)(struct file *, char *, size_t);
extern void proc_root_init(void);
extern void proc_flush_task(struct task_struct *);
......@@ -61,6 +63,16 @@ struct proc_dir_entry *proc_create_net_data(const char *name, umode_t mode,
struct proc_dir_entry *proc_create_net_single(const char *name, umode_t mode,
struct proc_dir_entry *parent,
int (*show)(struct seq_file *, void *), void *data);
struct proc_dir_entry *proc_create_net_data_write(const char *name, umode_t mode,
struct proc_dir_entry *parent,
const struct seq_operations *ops,
proc_write_t write,
unsigned int state_size, void *data);
struct proc_dir_entry *proc_create_net_single_write(const char *name, umode_t mode,
struct proc_dir_entry *parent,
int (*show)(struct seq_file *, void *),
proc_write_t write,
void *data);
#else /* CONFIG_PROC_FS */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment