From e44150157f42219fa5c074588efdb31ccfb197fc Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 6 Feb 2018 09:26:27 +0000 Subject: afs: Add missing afs_put_cell() afs_alloc_volume() needs to release the cell ref it obtained in the case of an error. Fix this by adding an afs_put_cell() call into the error path. This can triggered when a lookup for a cell in a dynamic root or an autocell mount returns an error whilst trying to look up the server (such as ENOMEDIUM). This results in an assertion failure oops when the module is unloaded due to outstanding refs on a cell record. Fixes: d2ddc776a458 ("afs: Overhaul volume and server record caching and fileserver rotation") Signed-off-by: David Howells cc: stable@vger.kernel.org --- fs/afs/volume.c | 1 + 1 file changed, 1 insertion(+) (limited to 'fs') diff --git a/fs/afs/volume.c b/fs/afs/volume.c index 684c48293353..79db9021f140 100644 --- a/fs/afs/volume.c +++ b/fs/afs/volume.c @@ -102,6 +102,7 @@ static struct afs_volume *afs_alloc_volume(struct afs_mount_params *params, error_2: afs_put_serverlist(params->net, slist); error_1: + afs_put_cell(params->net, volume->cell); kfree(volume); error_0: return ERR_PTR(ret); -- cgit From fe4d774c847398c2a45c10a780ccfde069840793 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 6 Feb 2018 06:26:30 +0000 Subject: afs: Fix missing cursor clearance afs_select_fileserver() ends the address cursor it is using in the case in which we get some sort of network error and run out of addresses to iterate through, before it jumps to try the next server. This also needs to be done when the server aborts with some sort of error that means we should try the next server. Fix this by: (1) Move the iterate_address afs_end_cursor() call to the next_server case. (2) End the cursor in the failed case. (3) Make afs_end_cursor() clear the ->begun flag and ->addr pointer in the address cursor. (4) Make afs_end_cursor() able to be called on an already cleared cursor. Without this, something like the following oops may occur: AFS: Assertion failed 18446612134397189888 == 0 is false 0xffff88007c279f00 == 0x0 is false ------------[ cut here ]------------ kernel BUG at fs/afs/rotate.c:360! RIP: 0010:afs_select_fileserver+0x79b/0xa30 [kafs] Call Trace: afs_statfs+0xcc/0x180 [kafs] ? p9_client_statfs+0x9e/0x110 [9pnet] ? _cond_resched+0x19/0x40 statfs_by_dentry+0x6d/0x90 vfs_statfs+0x1b/0xc0 user_statfs+0x4b/0x80 SYSC_statfs+0x15/0x30 SyS_statfs+0xe/0x10 entry_SYSCALL_64_fastpath+0x20/0x83 Fixes: d2ddc776a458 ("afs: Overhaul volume and server record caching and fileserver rotation") Reported-by: Marc Dionne Signed-off-by: David Howells cc: stable@vger.kernel.org --- fs/afs/addr_list.c | 13 ++++++++++--- fs/afs/rotate.c | 12 ++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) (limited to 'fs') diff --git a/fs/afs/addr_list.c b/fs/afs/addr_list.c index a537368ba0db..fd9f28b8a933 100644 --- a/fs/afs/addr_list.c +++ b/fs/afs/addr_list.c @@ -332,11 +332,18 @@ bool afs_iterate_addresses(struct afs_addr_cursor *ac) */ int afs_end_cursor(struct afs_addr_cursor *ac) { - if (ac->responded && ac->index != ac->start) - WRITE_ONCE(ac->alist->index, ac->index); + struct afs_addr_list *alist; + + alist = ac->alist; + if (alist) { + if (ac->responded && ac->index != ac->start) + WRITE_ONCE(alist->index, ac->index); + afs_put_addrlist(alist); + } - afs_put_addrlist(ac->alist); + ac->addr = NULL; ac->alist = NULL; + ac->begun = false; return ac->error; } diff --git a/fs/afs/rotate.c b/fs/afs/rotate.c index d04511fb3879..b473b25c6978 100644 --- a/fs/afs/rotate.c +++ b/fs/afs/rotate.c @@ -334,6 +334,7 @@ start: next_server: _debug("next"); + afs_end_cursor(&fc->ac); afs_put_cb_interest(afs_v2net(vnode), fc->cbi); fc->cbi = NULL; fc->index++; @@ -410,16 +411,15 @@ iterate_address: /* Iterate over the current server's address list to try and find an * address on which it will respond to us. */ - if (afs_iterate_addresses(&fc->ac)) { - _leave(" = t"); - return true; - } + if (!afs_iterate_addresses(&fc->ac)) + goto next_server; - afs_end_cursor(&fc->ac); - goto next_server; + _leave(" = t"); + return true; failed: fc->flags |= AFS_FS_CURSOR_STOP; + afs_end_cursor(&fc->ac); _leave(" = f [failed %d]", fc->ac.error); return false; } -- cgit From 8305e579c653b127b292fcdce551e930f9560260 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 6 Feb 2018 06:26:30 +0000 Subject: afs: Need to clear responded flag in addr cursor In afs_select_fileserver(), we need to clear the ->responded flag in the address list when reusing it. We should also clear it in afs_select_current_fileserver(). To this end, just memset() the object before initialising it. Fixes: d2ddc776a458 ("afs: Overhaul volume and server record caching and fileserver rotation") Signed-off-by: David Howells cc: stable@vger.kernel.org --- fs/afs/rotate.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'fs') diff --git a/fs/afs/rotate.c b/fs/afs/rotate.c index b473b25c6978..892a4904fd77 100644 --- a/fs/afs/rotate.c +++ b/fs/afs/rotate.c @@ -384,6 +384,7 @@ use_server: afs_get_addrlist(alist); read_unlock(&server->fs_lock); + memset(&fc->ac, 0, sizeof(fc->ac)); /* Probe the current fileserver if we haven't done so yet. */ if (!test_bit(AFS_SERVER_FL_PROBED, &server->flags)) { @@ -398,11 +399,8 @@ use_server: else afs_put_addrlist(alist); - fc->ac.addr = NULL; fc->ac.start = READ_ONCE(alist->index); fc->ac.index = fc->ac.start; - fc->ac.error = 0; - fc->ac.begun = false; goto iterate_address; iterate_address: @@ -458,12 +456,10 @@ bool afs_select_current_fileserver(struct afs_fs_cursor *fc) return false; } + memset(&fc->ac, 0, sizeof(fc->ac)); fc->ac.alist = alist; - fc->ac.addr = NULL; fc->ac.start = READ_ONCE(alist->index); fc->ac.index = fc->ac.start; - fc->ac.error = 0; - fc->ac.begun = false; goto iterate_address; case 0: -- cgit From 45df8462730d2149834980d3db16e2d2b9daaf60 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 6 Feb 2018 14:12:32 +0000 Subject: afs: Fix server list handling Fix server list handling in the following ways: (1) In afs_alloc_volume(), remove duplicate server list build code. This was already done by afs_alloc_server_list() which afs_alloc_volume() previously called. This just results in twice as many VL RPCs. (2) In afs_deliver_vl_get_entry_by_name_u(), use the number of server records indicated by ->nServers in the UVLDB record returned by the VL.GetEntryByNameU RPC call rather than scanning all NMAXNSERVERS slots. Unused slots may contain garbage. (3) In afs_alloc_server_list(), don't stop converting a UVLDB record into a server list just because we can't look up one of the servers. Just skip that server and go on to the next. If we can't look up any of the servers then we'll fail at the end. Without this patch, an attempt to view the umich.edu root cell using something like "ls /afs/umich.edu" on a dynamic root (future patch) mount or an autocell mount will result in ENOMEDIUM. The failure is due to kafs not stopping after nServers'worth of records have been read, but then trying to access a server with a garbage UUID and getting an error, which aborts the server list build. Fixes: d2ddc776a458 ("afs: Overhaul volume and server record caching and fileserver rotation") Reported-by: Jonathan Billings Signed-off-by: David Howells cc: stable@vger.kernel.org --- fs/afs/server_list.c | 3 ++- fs/afs/vlclient.c | 10 +++++++--- fs/afs/volume.c | 46 ++-------------------------------------------- 3 files changed, 11 insertions(+), 48 deletions(-) (limited to 'fs') diff --git a/fs/afs/server_list.c b/fs/afs/server_list.c index 0ab3f8457839..0f8dc4c8f07c 100644 --- a/fs/afs/server_list.c +++ b/fs/afs/server_list.c @@ -58,7 +58,8 @@ struct afs_server_list *afs_alloc_server_list(struct afs_cell *cell, server = afs_lookup_server(cell, key, &vldb->fs_server[i]); if (IS_ERR(server)) { ret = PTR_ERR(server); - if (ret == -ENOENT) + if (ret == -ENOENT || + ret == -ENOMEDIUM) continue; goto error_2; } diff --git a/fs/afs/vlclient.c b/fs/afs/vlclient.c index e372f89fd36a..5d8562f1ad4a 100644 --- a/fs/afs/vlclient.c +++ b/fs/afs/vlclient.c @@ -23,7 +23,7 @@ static int afs_deliver_vl_get_entry_by_name_u(struct afs_call *call) struct afs_uvldbentry__xdr *uvldb; struct afs_vldb_entry *entry; bool new_only = false; - u32 tmp; + u32 tmp, nr_servers; int i, ret; _enter(""); @@ -36,6 +36,10 @@ static int afs_deliver_vl_get_entry_by_name_u(struct afs_call *call) uvldb = call->buffer; entry = call->reply[0]; + nr_servers = ntohl(uvldb->nServers); + if (nr_servers > AFS_NMAXNSERVERS) + nr_servers = AFS_NMAXNSERVERS; + for (i = 0; i < ARRAY_SIZE(uvldb->name) - 1; i++) entry->name[i] = (u8)ntohl(uvldb->name[i]); entry->name[i] = 0; @@ -44,14 +48,14 @@ static int afs_deliver_vl_get_entry_by_name_u(struct afs_call *call) /* If there is a new replication site that we can use, ignore all the * sites that aren't marked as new. */ - for (i = 0; i < AFS_NMAXNSERVERS; i++) { + for (i = 0; i < nr_servers; i++) { tmp = ntohl(uvldb->serverFlags[i]); if (!(tmp & AFS_VLSF_DONTUSE) && (tmp & AFS_VLSF_NEWREPSITE)) new_only = true; } - for (i = 0; i < AFS_NMAXNSERVERS; i++) { + for (i = 0; i < nr_servers; i++) { struct afs_uuid__xdr *xdr; struct afs_uuid *uuid; int j; diff --git a/fs/afs/volume.c b/fs/afs/volume.c index 79db9021f140..b517a588781f 100644 --- a/fs/afs/volume.c +++ b/fs/afs/volume.c @@ -26,9 +26,8 @@ static struct afs_volume *afs_alloc_volume(struct afs_mount_params *params, unsigned long type_mask) { struct afs_server_list *slist; - struct afs_server *server; struct afs_volume *volume; - int ret = -ENOMEM, nr_servers = 0, i, j; + int ret = -ENOMEM, nr_servers = 0, i; for (i = 0; i < vldb->nr_servers; i++) if (vldb->fs_mask[i] & type_mask) @@ -58,49 +57,8 @@ static struct afs_volume *afs_alloc_volume(struct afs_mount_params *params, refcount_set(&slist->usage, 1); volume->servers = slist; - - /* Make sure a records exists for each server this volume occupies. */ - for (i = 0; i < nr_servers; i++) { - if (!(vldb->fs_mask[i] & type_mask)) - continue; - - server = afs_lookup_server(params->cell, params->key, - &vldb->fs_server[i]); - if (IS_ERR(server)) { - ret = PTR_ERR(server); - if (ret == -ENOENT) - continue; - goto error_2; - } - - /* Insertion-sort by server pointer */ - for (j = 0; j < slist->nr_servers; j++) - if (slist->servers[j].server >= server) - break; - if (j < slist->nr_servers) { - if (slist->servers[j].server == server) { - afs_put_server(params->net, server); - continue; - } - - memmove(slist->servers + j + 1, - slist->servers + j, - (slist->nr_servers - j) * sizeof(struct afs_server_entry)); - } - - slist->servers[j].server = server; - slist->nr_servers++; - } - - if (slist->nr_servers == 0) { - ret = -EDESTADDRREQ; - goto error_2; - } - return volume; -error_2: - afs_put_serverlist(params->net, slist); error_1: afs_put_cell(params->net, volume->cell); kfree(volume); @@ -328,7 +286,7 @@ static int afs_update_volume_status(struct afs_volume *volume, struct key *key) /* See if the volume's server list got updated. */ new = afs_alloc_server_list(volume->cell, key, - vldb, (1 << volume->type)); + vldb, (1 << volume->type)); if (IS_ERR(new)) { ret = PTR_ERR(new); goto error_vldb; -- cgit From 63dc4e4aa5ab61868199960979fe1feecdabd01e Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 6 Feb 2018 06:26:30 +0000 Subject: afs: Remove unused code Remove some old unused code. Signed-off-by: David Howells --- fs/afs/rotate.c | 235 -------------------------------------------------------- 1 file changed, 235 deletions(-) (limited to 'fs') diff --git a/fs/afs/rotate.c b/fs/afs/rotate.c index 892a4904fd77..cfe0931e7844 100644 --- a/fs/afs/rotate.c +++ b/fs/afs/rotate.c @@ -516,238 +516,3 @@ int afs_end_vnode_operation(struct afs_fs_cursor *fc) return fc->ac.error; } - -#if 0 -/* - * Set a filesystem server cursor for using a specific FS server. - */ -int afs_set_fs_cursor(struct afs_fs_cursor *fc, struct afs_vnode *vnode) -{ - afs_init_fs_cursor(fc, vnode); - - read_seqlock_excl(&vnode->cb_lock); - if (vnode->cb_interest) { - if (vnode->cb_interest->server->fs_state == 0) - fc->server = afs_get_server(vnode->cb_interest->server); - else - fc->ac.error = vnode->cb_interest->server->fs_state; - } else { - fc->ac.error = -ESTALE; - } - read_sequnlock_excl(&vnode->cb_lock); - - return fc->ac.error; -} - -/* - * pick a server to use to try accessing this volume - * - returns with an elevated usage count on the server chosen - */ -bool afs_volume_pick_fileserver(struct afs_fs_cursor *fc, struct afs_vnode *vnode) -{ - struct afs_volume *volume = vnode->volume; - struct afs_server *server; - int ret, state, loop; - - _enter("%s", volume->vlocation->vldb.name); - - /* stick with the server we're already using if we can */ - if (vnode->cb_interest && vnode->cb_interest->server->fs_state == 0) { - fc->server = afs_get_server(vnode->cb_interest->server); - goto set_server; - } - - down_read(&volume->server_sem); - - /* handle the no-server case */ - if (volume->nservers == 0) { - fc->ac.error = volume->rjservers ? -ENOMEDIUM : -ESTALE; - up_read(&volume->server_sem); - _leave(" = f [no servers %d]", fc->ac.error); - return false; - } - - /* basically, just search the list for the first live server and use - * that */ - ret = 0; - for (loop = 0; loop < volume->nservers; loop++) { - server = volume->servers[loop]; - state = server->fs_state; - - _debug("consider %d [%d]", loop, state); - - switch (state) { - case 0: - goto picked_server; - - case -ENETUNREACH: - if (ret == 0) - ret = state; - break; - - case -EHOSTUNREACH: - if (ret == 0 || - ret == -ENETUNREACH) - ret = state; - break; - - case -ECONNREFUSED: - if (ret == 0 || - ret == -ENETUNREACH || - ret == -EHOSTUNREACH) - ret = state; - break; - - default: - case -EREMOTEIO: - if (ret == 0 || - ret == -ENETUNREACH || - ret == -EHOSTUNREACH || - ret == -ECONNREFUSED) - ret = state; - break; - } - } - -error: - fc->ac.error = ret; - - /* no available servers - * - TODO: handle the no active servers case better - */ - up_read(&volume->server_sem); - _leave(" = f [%d]", fc->ac.error); - return false; - -picked_server: - /* Found an apparently healthy server. We need to register an interest - * in receiving callbacks before we talk to it. - */ - ret = afs_register_server_cb_interest(vnode, - &volume->cb_interests[loop], server); - if (ret < 0) - goto error; - - fc->server = afs_get_server(server); - up_read(&volume->server_sem); -set_server: - fc->ac.alist = afs_get_addrlist(fc->server->addrs); - fc->ac.addr = &fc->ac.alist->addrs[0]; - _debug("USING SERVER: %pIS\n", &fc->ac.addr->transport); - _leave(" = t (picked %pIS)", &fc->ac.addr->transport); - return true; -} - -/* - * release a server after use - * - releases the ref on the server struct that was acquired by picking - * - records result of using a particular server to access a volume - * - return true to try again, false if okay or to issue error - * - the caller must release the server struct if result was false - */ -bool afs_iterate_fs_cursor(struct afs_fs_cursor *fc, - struct afs_vnode *vnode) -{ - struct afs_volume *volume = vnode->volume; - struct afs_server *server = fc->server; - unsigned loop; - - _enter("%s,%pIS,%d", - volume->vlocation->vldb.name, &fc->ac.addr->transport, - fc->ac.error); - - switch (fc->ac.error) { - /* success */ - case 0: - server->fs_state = 0; - _leave(" = f"); - return false; - - /* the fileserver denied all knowledge of the volume */ - case -ENOMEDIUM: - down_write(&volume->server_sem); - - /* firstly, find where the server is in the active list (if it - * is) */ - for (loop = 0; loop < volume->nservers; loop++) - if (volume->servers[loop] == server) - goto present; - - /* no longer there - may have been discarded by another op */ - goto try_next_server_upw; - - present: - volume->nservers--; - memmove(&volume->servers[loop], - &volume->servers[loop + 1], - sizeof(volume->servers[loop]) * - (volume->nservers - loop)); - volume->servers[volume->nservers] = NULL; - afs_put_server(afs_v2net(vnode), server); - volume->rjservers++; - - if (volume->nservers > 0) - /* another server might acknowledge its existence */ - goto try_next_server_upw; - - /* handle the case where all the fileservers have rejected the - * volume - * - TODO: try asking the fileservers for volume information - * - TODO: contact the VL server again to see if the volume is - * no longer registered - */ - up_write(&volume->server_sem); - afs_put_server(afs_v2net(vnode), server); - fc->server = NULL; - _leave(" = f [completely rejected]"); - return false; - - /* problem reaching the server */ - case -ENETUNREACH: - case -EHOSTUNREACH: - case -ECONNREFUSED: - case -ETIME: - case -ETIMEDOUT: - case -EREMOTEIO: - /* mark the server as dead - * TODO: vary dead timeout depending on error - */ - spin_lock(&server->fs_lock); - if (!server->fs_state) { - server->fs_state = fc->ac.error; - printk("kAFS: SERVER DEAD state=%d\n", fc->ac.error); - } - spin_unlock(&server->fs_lock); - goto try_next_server; - - /* miscellaneous error */ - default: - case -ENOMEM: - case -ENONET: - /* tell the caller to accept the result */ - afs_put_server(afs_v2net(vnode), server); - fc->server = NULL; - _leave(" = f [local failure]"); - return false; - } - - /* tell the caller to loop around and try the next server */ -try_next_server_upw: - up_write(&volume->server_sem); -try_next_server: - afs_put_server(afs_v2net(vnode), server); - _leave(" = t [try next server]"); - return true; -} - -/* - * Clean up a fileserver cursor. - */ -int afs_end_fs_cursor(struct afs_fs_cursor *fc, struct afs_net *net) -{ - afs_end_cursor(&fc->ac); - afs_put_server(net, fc->server); - return fc->ac.error; -} - -#endif -- cgit From 16280a15be751b9994e94c5dc944e93fa4293199 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 6 Feb 2018 06:26:30 +0000 Subject: afs: Rearrange afs_select_fileserver() a little Rearrange afs_select_fileserver() a little to put the use_server chunk before the next_server chunk so that with the removal of a couple of gotos the main path through the function is all one sequence. Signed-off-by: David Howells --- fs/afs/rotate.c | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) (limited to 'fs') diff --git a/fs/afs/rotate.c b/fs/afs/rotate.c index cfe0931e7844..ad1328d85526 100644 --- a/fs/afs/rotate.c +++ b/fs/afs/rotate.c @@ -330,27 +330,6 @@ start: if (!afs_start_fs_iteration(fc, vnode)) goto failed; - goto use_server; - -next_server: - _debug("next"); - afs_end_cursor(&fc->ac); - afs_put_cb_interest(afs_v2net(vnode), fc->cbi); - fc->cbi = NULL; - fc->index++; - if (fc->index >= fc->server_list->nr_servers) - fc->index = 0; - if (fc->index != fc->start) - goto use_server; - - /* That's all the servers poked to no good effect. Try again if some - * of them were busy. - */ - if (fc->flags & AFS_FS_CURSOR_VBUSY) - goto restart_from_beginning; - - fc->ac.error = -EDESTADDRREQ; - goto failed; use_server: _debug("use"); @@ -401,7 +380,6 @@ use_server: fc->ac.start = READ_ONCE(alist->index); fc->ac.index = fc->ac.start; - goto iterate_address; iterate_address: ASSERT(fc->ac.alist); @@ -415,6 +393,26 @@ iterate_address: _leave(" = t"); return true; +next_server: + _debug("next"); + afs_end_cursor(&fc->ac); + afs_put_cb_interest(afs_v2net(vnode), fc->cbi); + fc->cbi = NULL; + fc->index++; + if (fc->index >= fc->server_list->nr_servers) + fc->index = 0; + if (fc->index != fc->start) + goto use_server; + + /* That's all the servers poked to no good effect. Try again if some + * of them were busy. + */ + if (fc->flags & AFS_FS_CURSOR_VBUSY) + goto restart_from_beginning; + + fc->ac.error = -EDESTADDRREQ; + goto failed; + failed: fc->flags |= AFS_FS_CURSOR_STOP; afs_end_cursor(&fc->ac); -- cgit From 4d673da14533b32fe8d3125b5b7be4fea14e39a8 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 6 Feb 2018 06:26:30 +0000 Subject: afs: Support the AFS dynamic root Support the AFS dynamic root which is a pseudo-volume that doesn't connect to any server resource, but rather is just a root directory that dynamically creates mountpoint directories where the name of such a directory is the name of the cell. Such a mount can be created thus: mount -t afs none /afs -o dyn Dynamic root superblocks aren't shared except by bind mounts and propagation. Cell root volumes can then be mounted by referring to them by name, e.g.: ls /afs/grand.central.org/ ls /afs/.grand.central.org/ The kernel will upcall to consult the DNS if the address wasn't supplied directly. Signed-off-by: David Howells --- fs/afs/dir.c | 122 +++++++++++++++++++++++++++++++++++++++++++------ fs/afs/inode.c | 48 ++++++++++++-------- fs/afs/internal.h | 12 +++-- fs/afs/mntpt.c | 20 +++++---- fs/afs/super.c | 132 +++++++++++++++++++++++++++++++++++++----------------- 5 files changed, 247 insertions(+), 87 deletions(-) (limited to 'fs') diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 23c7f395d718..ba2b458b36d1 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -17,10 +17,13 @@ #include #include #include +#include #include "internal.h" static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); +static struct dentry *afs_dynroot_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags); static int afs_dir_open(struct inode *inode, struct file *file); static int afs_readdir(struct file *file, struct dir_context *ctx); static int afs_d_revalidate(struct dentry *dentry, unsigned int flags); @@ -64,6 +67,17 @@ const struct inode_operations afs_dir_inode_operations = { .listxattr = afs_listxattr, }; +const struct file_operations afs_dynroot_file_operations = { + .open = dcache_dir_open, + .release = dcache_dir_close, + .iterate_shared = dcache_readdir, + .llseek = dcache_dir_lseek, +}; + +const struct inode_operations afs_dynroot_inode_operations = { + .lookup = afs_dynroot_lookup, +}; + const struct dentry_operations afs_fs_dentry_operations = { .d_revalidate = afs_d_revalidate, .d_delete = afs_d_delete, @@ -467,26 +481,59 @@ static int afs_do_lookup(struct inode *dir, struct dentry *dentry, return 0; } +/* + * Probe to see if a cell may exist. This prevents positive dentries from + * being created unnecessarily. + */ +static int afs_probe_cell_name(struct dentry *dentry) +{ + struct afs_cell *cell; + const char *name = dentry->d_name.name; + size_t len = dentry->d_name.len; + int ret; + + /* Names prefixed with a dot are R/W mounts. */ + if (name[0] == '.') { + if (len == 1) + return -EINVAL; + name++; + len--; + } + + cell = afs_lookup_cell_rcu(afs_d2net(dentry), name, len); + if (!IS_ERR(cell)) { + afs_put_cell(afs_d2net(dentry), cell); + return 0; + } + + ret = dns_query("afsdb", name, len, "ipv4", NULL, NULL); + if (ret == -ENODATA) + ret = -EDESTADDRREQ; + return ret; +} + /* * Try to auto mount the mountpoint with pseudo directory, if the autocell * operation is setted. */ -static struct inode *afs_try_auto_mntpt( - int ret, struct dentry *dentry, struct inode *dir, struct key *key, - struct afs_fid *fid) +static struct inode *afs_try_auto_mntpt(struct dentry *dentry, + struct inode *dir, struct afs_fid *fid) { - const char *devname = dentry->d_name.name; struct afs_vnode *vnode = AFS_FS_I(dir); struct inode *inode; + int ret = -ENOENT; - _enter("%d, %p{%pd}, {%x:%u}, %p", - ret, dentry, dentry, vnode->fid.vid, vnode->fid.vnode, key); + _enter("%p{%pd}, {%x:%u}", + dentry, dentry, vnode->fid.vid, vnode->fid.vnode); + + if (!test_bit(AFS_VNODE_AUTOCELL, &vnode->flags)) + goto out; - if (ret != -ENOENT || - !test_bit(AFS_VNODE_AUTOCELL, &vnode->flags)) + ret = afs_probe_cell_name(dentry); + if (ret < 0) goto out; - inode = afs_iget_autocell(dir, devname, strlen(devname), key); + inode = afs_iget_pseudo_dir(dir->i_sb, false); if (IS_ERR(inode)) { ret = PTR_ERR(inode); goto out; @@ -545,13 +592,16 @@ static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry, ret = afs_do_lookup(dir, dentry, &fid, key); if (ret < 0) { - inode = afs_try_auto_mntpt(ret, dentry, dir, key, &fid); - if (!IS_ERR(inode)) { - key_put(key); - goto success; + if (ret == -ENOENT) { + inode = afs_try_auto_mntpt(dentry, dir, &fid); + if (!IS_ERR(inode)) { + key_put(key); + goto success; + } + + ret = PTR_ERR(inode); } - ret = PTR_ERR(inode); key_put(key); if (ret == -ENOENT) { d_add(dentry, NULL); @@ -582,6 +632,46 @@ success: return NULL; } +/* + * Look up an entry in a dynroot directory. + */ +static struct dentry *afs_dynroot_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + struct afs_vnode *vnode; + struct afs_fid fid; + struct inode *inode; + int ret; + + vnode = AFS_FS_I(dir); + + _enter("%pd", dentry); + + ASSERTCMP(d_inode(dentry), ==, NULL); + + if (dentry->d_name.len >= AFSNAMEMAX) { + _leave(" = -ENAMETOOLONG"); + return ERR_PTR(-ENAMETOOLONG); + } + + inode = afs_try_auto_mntpt(dentry, dir, &fid); + if (IS_ERR(inode)) { + ret = PTR_ERR(inode); + if (ret == -ENOENT) { + d_add(dentry, NULL); + _leave(" = NULL [negative]"); + return NULL; + } + _leave(" = %d [do]", ret); + return ERR_PTR(ret); + } + + d_add(dentry, inode); + _leave(" = 0 { ino=%lu v=%u }", + d_inode(dentry)->i_ino, d_inode(dentry)->i_generation); + return NULL; +} + /* * check that a dentry lookup hit has found a valid entry * - NOTE! the hit can be a negative hit too, so we can't assume we have an @@ -589,6 +679,7 @@ success: */ static int afs_d_revalidate(struct dentry *dentry, unsigned int flags) { + struct afs_super_info *as = dentry->d_sb->s_fs_info; struct afs_vnode *vnode, *dir; struct afs_fid uninitialized_var(fid); struct dentry *parent; @@ -600,6 +691,9 @@ static int afs_d_revalidate(struct dentry *dentry, unsigned int flags) if (flags & LOOKUP_RCU) return -ECHILD; + if (as->dyn_root) + return 1; + if (d_really_is_positive(dentry)) { vnode = AFS_FS_I(d_inode(dentry)); _enter("{v={%x:%u} n=%pd fl=%lx},", diff --git a/fs/afs/inode.c b/fs/afs/inode.c index c7f17c44c7ce..6b39d0255b72 100644 --- a/fs/afs/inode.c +++ b/fs/afs/inode.c @@ -147,7 +147,7 @@ int afs_iget5_test(struct inode *inode, void *opaque) * * These pseudo inodes don't match anything. */ -static int afs_iget5_autocell_test(struct inode *inode, void *opaque) +static int afs_iget5_pseudo_dir_test(struct inode *inode, void *opaque) { return 0; } @@ -169,31 +169,34 @@ static int afs_iget5_set(struct inode *inode, void *opaque) } /* - * inode retrieval for autocell + * Create an inode for a dynamic root directory or an autocell dynamic + * automount dir. */ -struct inode *afs_iget_autocell(struct inode *dir, const char *dev_name, - int namesz, struct key *key) +struct inode *afs_iget_pseudo_dir(struct super_block *sb, bool root) { struct afs_iget_data data; struct afs_super_info *as; struct afs_vnode *vnode; - struct super_block *sb; struct inode *inode; static atomic_t afs_autocell_ino; - _enter("{%x:%u},%*.*s,", - AFS_FS_I(dir)->fid.vid, AFS_FS_I(dir)->fid.vnode, - namesz, namesz, dev_name ?: ""); + _enter(""); - sb = dir->i_sb; as = sb->s_fs_info; - data.volume = as->volume; - data.fid.vid = as->volume->vid; - data.fid.unique = 0; - data.fid.vnode = 0; + if (as->volume) { + data.volume = as->volume; + data.fid.vid = as->volume->vid; + } + if (root) { + data.fid.vnode = 1; + data.fid.unique = 1; + } else { + data.fid.vnode = atomic_inc_return(&afs_autocell_ino); + data.fid.unique = 0; + } - inode = iget5_locked(sb, atomic_inc_return(&afs_autocell_ino), - afs_iget5_autocell_test, afs_iget5_set, + inode = iget5_locked(sb, data.fid.vnode, + afs_iget5_pseudo_dir_test, afs_iget5_set, &data); if (!inode) { _leave(" = -ENOMEM"); @@ -211,7 +214,12 @@ struct inode *afs_iget_autocell(struct inode *dir, const char *dev_name, inode->i_size = 0; inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO; - inode->i_op = &afs_autocell_inode_operations; + if (root) { + inode->i_op = &afs_dynroot_inode_operations; + inode->i_fop = &afs_dynroot_file_operations; + } else { + inode->i_op = &afs_autocell_inode_operations; + } set_nlink(inode, 2); inode->i_uid = GLOBAL_ROOT_UID; inode->i_gid = GLOBAL_ROOT_GID; @@ -223,8 +231,12 @@ struct inode *afs_iget_autocell(struct inode *dir, const char *dev_name, inode->i_generation = 0; set_bit(AFS_VNODE_PSEUDODIR, &vnode->flags); - set_bit(AFS_VNODE_MOUNTPOINT, &vnode->flags); - inode->i_flags |= S_AUTOMOUNT | S_NOATIME; + if (!root) { + set_bit(AFS_VNODE_MOUNTPOINT, &vnode->flags); + inode->i_flags |= S_AUTOMOUNT; + } + + inode->i_flags |= S_NOATIME; unlock_new_inode(inode); _leave(" = %p", inode); return inode; diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 804d1f905622..f38d6a561a84 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -36,6 +36,7 @@ struct afs_mount_params { bool rwpath; /* T if the parent should be considered R/W */ bool force; /* T to force cell type */ bool autocell; /* T if set auto mount operation */ + bool dyn_root; /* T if dynamic root */ afs_voltype_t type; /* type of volume requested */ int volnamesz; /* size of volume name */ const char *volname; /* name of volume to mount */ @@ -186,6 +187,7 @@ struct afs_super_info { struct afs_net *net; /* 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 */ }; static inline struct afs_super_info *AFS_FS_S(struct super_block *sb) @@ -634,10 +636,13 @@ extern bool afs_cm_incoming_call(struct afs_call *); /* * dir.c */ -extern bool afs_dir_check_page(struct inode *, struct page *); +extern const struct file_operations afs_dir_file_operations; extern const struct inode_operations afs_dir_inode_operations; +extern const struct file_operations afs_dynroot_file_operations; +extern const struct inode_operations afs_dynroot_inode_operations; extern const struct dentry_operations afs_fs_dentry_operations; -extern const struct file_operations afs_dir_file_operations; + +extern bool afs_dir_check_page(struct inode *, struct page *); /* * file.c @@ -695,8 +700,7 @@ extern int afs_fs_get_capabilities(struct afs_net *, struct afs_server *, */ extern int afs_fetch_status(struct afs_vnode *, struct key *); extern int afs_iget5_test(struct inode *, void *); -extern struct inode *afs_iget_autocell(struct inode *, const char *, int, - struct key *); +extern struct inode *afs_iget_pseudo_dir(struct super_block *, bool); extern struct inode *afs_iget(struct super_block *, struct key *, struct afs_fid *, struct afs_file_status *, struct afs_callback *, diff --git a/fs/afs/mntpt.c b/fs/afs/mntpt.c index 690fea9d84c3..99fd13500a97 100644 --- a/fs/afs/mntpt.c +++ b/fs/afs/mntpt.c @@ -72,7 +72,7 @@ static int afs_mntpt_open(struct inode *inode, struct file *file) */ static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt) { - struct afs_super_info *super; + struct afs_super_info *as; struct vfsmount *mnt; struct afs_vnode *vnode; struct page *page; @@ -104,13 +104,13 @@ static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt) goto error_no_page; if (mntpt->d_name.name[0] == '.') { - devname[0] = '#'; - memcpy(devname + 1, mntpt->d_name.name, size - 1); + devname[0] = '%'; + memcpy(devname + 1, mntpt->d_name.name + 1, size - 1); memcpy(devname + size, afs_root_cell, sizeof(afs_root_cell)); rwpath = true; } else { - devname[0] = '%'; + devname[0] = '#'; memcpy(devname + 1, mntpt->d_name.name, size); memcpy(devname + size + 1, afs_root_cell, sizeof(afs_root_cell)); @@ -142,11 +142,13 @@ static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt) } /* work out what options we want */ - super = AFS_FS_S(mntpt->d_sb); - memcpy(options, "cell=", 5); - strcpy(options + 5, super->volume->cell->name); - if (super->volume->type == AFSVL_RWVOL || rwpath) - strcat(options, ",rwpath"); + as = AFS_FS_S(mntpt->d_sb); + if (as->cell) { + memcpy(options, "cell=", 5); + strcpy(options + 5, as->cell->name); + if ((as->volume && as->volume->type == AFSVL_RWVOL) || rwpath) + strcat(options, ",rwpath"); + } /* try and do the mount */ _debug("--- attempting mount %s -o %s ---", devname, options); diff --git a/fs/afs/super.c b/fs/afs/super.c index 1037dd41a622..3623c952b6ff 100644 --- a/fs/afs/super.c +++ b/fs/afs/super.c @@ -64,6 +64,7 @@ static atomic_t afs_count_active_inodes; enum { afs_no_opt, afs_opt_cell, + afs_opt_dyn, afs_opt_rwpath, afs_opt_vol, afs_opt_autocell, @@ -71,6 +72,7 @@ enum { static const match_table_t afs_options_list = { { afs_opt_cell, "cell=%s" }, + { afs_opt_dyn, "dyn" }, { afs_opt_rwpath, "rwpath" }, { afs_opt_vol, "vol=%s" }, { afs_opt_autocell, "autocell" }, @@ -148,6 +150,11 @@ static int afs_show_devname(struct seq_file *m, struct dentry *root) const char *suf = ""; char pref = '%'; + if (as->dyn_root) { + seq_puts(m, "none"); + return 0; + } + switch (volume->type) { case AFSVL_RWVOL: break; @@ -171,8 +178,12 @@ static int afs_show_devname(struct seq_file *m, struct dentry *root) */ static int afs_show_options(struct seq_file *m, struct dentry *root) { + struct afs_super_info *as = AFS_FS_S(root->d_sb); + + if (as->dyn_root) + seq_puts(m, ",dyn"); if (test_bit(AFS_VNODE_AUTOCELL, &AFS_FS_I(d_inode(root))->flags)) - seq_puts(m, "autocell"); + seq_puts(m, ",autocell"); return 0; } @@ -212,7 +223,7 @@ static int afs_parse_options(struct afs_mount_params *params, break; case afs_opt_rwpath: - params->rwpath = 1; + params->rwpath = true; break; case afs_opt_vol: @@ -220,7 +231,11 @@ static int afs_parse_options(struct afs_mount_params *params, break; case afs_opt_autocell: - params->autocell = 1; + params->autocell = true; + break; + + case afs_opt_dyn: + params->dyn_root = true; break; default: @@ -254,7 +269,7 @@ static int afs_parse_device_name(struct afs_mount_params *params, int cellnamesz; _enter(",%s", name); - + if (!name) { printk(KERN_ERR "kAFS: no volume name specified\n"); return -EINVAL; @@ -336,7 +351,14 @@ 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 && as->volume->vid == as1->volume->vid; + return (as->net == as1->net && + as->volume && + as->volume->vid == as1->volume->vid); +} + +static int afs_dynroot_test_super(struct super_block *sb, void *data) +{ + return false; } static int afs_set_super(struct super_block *sb, void *data) @@ -365,24 +387,30 @@ static int afs_fill_super(struct super_block *sb, sb->s_blocksize_bits = PAGE_SHIFT; sb->s_magic = AFS_FS_MAGIC; sb->s_op = &afs_super_ops; - sb->s_xattr = afs_xattr_handlers; + if (!as->dyn_root) + sb->s_xattr = afs_xattr_handlers; ret = super_setup_bdi(sb); if (ret) return ret; sb->s_bdi->ra_pages = VM_MAX_READAHEAD * 1024 / PAGE_SIZE; - sprintf(sb->s_id, "%u", as->volume->vid); - - afs_activate_volume(as->volume); /* allocate the root inode and dentry */ - fid.vid = as->volume->vid; - fid.vnode = 1; - fid.unique = 1; - inode = afs_iget(sb, params->key, &fid, NULL, NULL, NULL); + if (as->dyn_root) { + inode = afs_iget_pseudo_dir(sb, true); + sb->s_flags |= SB_RDONLY; + } else { + sprintf(sb->s_id, "%u", as->volume->vid); + afs_activate_volume(as->volume); + fid.vid = as->volume->vid; + fid.vnode = 1; + fid.unique = 1; + inode = afs_iget(sb, params->key, &fid, NULL, NULL, NULL); + } + if (IS_ERR(inode)) return PTR_ERR(inode); - if (params->autocell) + if (params->autocell || params->dyn_root) set_bit(AFS_VNODE_AUTOCELL, &AFS_FS_I(inode)->flags); ret = -ENOMEM; @@ -407,7 +435,10 @@ 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->cell = afs_get_cell(params->cell); + if (params->dyn_root) + as->dyn_root = true; + else + as->cell = afs_get_cell(params->cell); } return as; } @@ -451,18 +482,20 @@ static struct dentry *afs_mount(struct file_system_type *fs_type, goto error; } - ret = afs_parse_device_name(¶ms, dev_name); - if (ret < 0) - goto error; + if (!params.dyn_root) { + ret = afs_parse_device_name(¶ms, dev_name); + if (ret < 0) + goto error; - /* try and do the mount securely */ - key = afs_request_key(params.cell); - if (IS_ERR(key)) { - _leave(" = %ld [key]", PTR_ERR(key)); - ret = PTR_ERR(key); - goto error; + /* try and do the mount securely */ + key = afs_request_key(params.cell); + if (IS_ERR(key)) { + _leave(" = %ld [key]", PTR_ERR(key)); + ret = PTR_ERR(key); + goto error; + } + params.key = key; } - params.key = key; /* allocate a superblock info record */ ret = -ENOMEM; @@ -470,20 +503,25 @@ static struct dentry *afs_mount(struct file_system_type *fs_type, if (!as) goto error_key; - /* Assume we're going to need a volume record; at the very least we can - * use it to update the volume record if we have one already. This - * checks that the volume exists within the cell. - */ - candidate = afs_create_volume(¶ms); - if (IS_ERR(candidate)) { - ret = PTR_ERR(candidate); - goto error_as; - } + if (!params.dyn_root) { + /* Assume we're going to need a volume record; at the very + * least we can use it to update the volume record if we have + * one already. This checks that the volume exists within the + * cell. + */ + candidate = afs_create_volume(¶ms); + if (IS_ERR(candidate)) { + ret = PTR_ERR(candidate); + goto error_as; + } - as->volume = candidate; + as->volume = candidate; + } /* allocate a deviceless superblock */ - sb = sget(fs_type, afs_test_super, afs_set_super, flags, as); + sb = sget(fs_type, + as->dyn_root ? afs_dynroot_test_super : afs_test_super, + afs_set_super, flags, as); if (IS_ERR(sb)) { ret = PTR_ERR(sb); goto error_as; @@ -529,9 +567,11 @@ static void afs_kill_super(struct super_block *sb) /* Clear the callback interests (which will do ilookup5) before * deactivating the superblock. */ - afs_clear_callback_interests(as->net, as->volume->servers); + if (as->volume) + afs_clear_callback_interests(as->net, as->volume->servers); kill_anon_super(sb); - afs_deactivate_volume(as->volume); + if (as->volume) + afs_deactivate_volume(as->volume); afs_destroy_sbi(as); } @@ -619,12 +659,24 @@ static void afs_destroy_inode(struct inode *inode) */ static int afs_statfs(struct dentry *dentry, struct kstatfs *buf) { + struct afs_super_info *as = AFS_FS_S(dentry->d_sb); struct afs_fs_cursor fc; struct afs_volume_status vs; struct afs_vnode *vnode = AFS_FS_I(d_inode(dentry)); struct key *key; int ret; + buf->f_type = dentry->d_sb->s_magic; + buf->f_bsize = AFS_BLOCK_SIZE; + buf->f_namelen = AFSNAMEMAX - 1; + + if (as->dyn_root) { + buf->f_blocks = 1; + buf->f_bavail = 0; + buf->f_bfree = 0; + return 0; + } + key = afs_request_key(vnode->volume->cell); if (IS_ERR(key)) return PTR_ERR(key); @@ -645,10 +697,6 @@ static int afs_statfs(struct dentry *dentry, struct kstatfs *buf) key_put(key); if (ret == 0) { - buf->f_type = dentry->d_sb->s_magic; - buf->f_bsize = AFS_BLOCK_SIZE; - buf->f_namelen = AFSNAMEMAX - 1; - if (vs.max_quota == 0) buf->f_blocks = vs.part_max_blocks; else -- cgit