[RFC PATCH] afs: Create a mountpoint through symlink() and remove through rmdir()

From: David Howells
Date: Wed Feb 19 2020 - 19:11:09 EST


If symlink() is given a magic prefix in the target string, turn it into a
mountpoint instead.

The prefix is "//_afs3_mount:". POSIX says that a double slash at the
beginning of a filename is special:

A pathname consisting of a single slash shall resolve to the root
directory of the process. A null pathname shall not be successfully
resolved. A pathname that begins with two successive slashes may be
interpreted in an implementation-defined manner, although more than two
leading slashes shall be treated as a single slash.

however, that might be validly interpreted by Windows as a UNC name. So
the prefix is made a bit more than that to make that less likely. The
prefix is then stripped off and a "." is added for transmission to the
server.

afs_mntpt_lookup() is removed so that the new dentry is marked as being an
autodir type. afs_rmdir() then checks for this and switches over to
afs_unlink() to remove a mountpoint (as far as the server is concerned,
it's not a directory). The unlink() system call can't be used to remove
the mountpoint without alteration to the core VFS as may_delete() throws an
error. This allows rmdir() to remove an automount point (provided it's not
mounted over).

Note that this behaviour varies from other AFS implementations in that the
proposed symlink *could* be valid content (though it seems unlikely) and in
those implementations rmdir can't remove a mountpoint. Since the Linux
symlink() system can handle a target string of 4095 characters, but AFS can
only handle a symlink of 1024 chars, it might be better to make the magic
prefix large enough to force the target string length over the server
limit.

Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
---

fs/afs/dir.c | 38 +++++++++++++++++++++++++++++++++++---
fs/afs/fsclient.c | 9 +++++----
fs/afs/internal.h | 6 ++++--
fs/afs/mntpt.c | 15 ---------------
fs/afs/yfsclient.c | 7 ++++---
5 files changed, 48 insertions(+), 27 deletions(-)

diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 5c794f4b051a..40552036cd6a 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -1365,6 +1365,9 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry)
_enter("{%llx:%llu},{%pd}",
dvnode->fid.vid, dvnode->fid.vnode, dentry);

+ if (d_is_autodir(dentry))
+ return afs_unlink(dir, dentry);
+
scb = kzalloc(sizeof(struct afs_status_cb), GFP_KERNEL);
if (!scb)
return -ENOMEM;
@@ -1721,17 +1724,21 @@ static int afs_link(struct dentry *from, struct inode *dir,
return ret;
}

+static const char afs_mount_prefix[] = "//_afs3_mount:";
+
/*
- * create a symlink in an AFS filesystem
+ * create a symlink or a mountpoint in an AFS filesystem
*/
static int afs_symlink(struct inode *dir, struct dentry *dentry,
- const char *content)
+ const char *specified_content)
{
struct afs_iget_data iget_data;
struct afs_fs_cursor fc;
struct afs_status_cb *scb;
struct afs_vnode *dvnode = AFS_FS_I(dir);
struct key *key;
+ char *content = (char *)specified_content;
+ bool is_mountpoint = false;
int ret;

_enter("{%llx:%llu},{%pd},%s",
@@ -1746,6 +1753,28 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry,
if (strlen(content) >= AFSPATHMAX)
goto error;

+ if (memcmp(content, afs_mount_prefix,
+ sizeof(afs_mount_prefix) - 1) == 0) {
+ /* This is going to be a mountpoint. */
+ char *p = content + sizeof(afs_mount_prefix) - 1, *c;
+ size_t clen = strlen(p);
+
+ if (clen < 2 ||
+ (p[0] != '%' && p[0] != '#'))
+ goto error;
+
+ /* Snip off the prefix and append a dot */
+ ret = -ENOMEM;
+ c = kmalloc(clen + 2, GFP_KERNEL);
+ if (!c)
+ goto error;
+ memcpy(c, p, clen);
+ c[clen] = '.';
+ c[clen + 1] = 0;
+ content = c;
+ is_mountpoint = true;
+ }
+
ret = -ENOMEM;
scb = kcalloc(2, sizeof(struct afs_status_cb), GFP_KERNEL);
if (!scb)
@@ -1765,7 +1794,8 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry,
fc.cb_break = afs_calc_vnode_cb_break(dvnode);
afs_prep_for_new_inode(&fc, &iget_data);
afs_fs_symlink(&fc, dentry->d_name.name, content,
- &scb[0], &iget_data.fid, &scb[1]);
+ &scb[0], &iget_data.fid, &scb[1],
+ is_mountpoint);
}

afs_check_for_remote_deletion(&fc, dvnode);
@@ -1794,6 +1824,8 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry,
error_scb:
kfree(scb);
error:
+ if (content != specified_content)
+ kfree(content);
d_drop(dentry);
_leave(" = %d", ret);
return ret;
diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c
index 1f9c5d8e6fe5..e2a2abe3a9aa 100644
--- a/fs/afs/fsclient.c
+++ b/fs/afs/fsclient.c
@@ -896,14 +896,15 @@ static const struct afs_call_type afs_RXFSSymlink = {
};

/*
- * create a symbolic link
+ * Create a symbolic link or a mountpoint (differentiated by mode).
*/
int afs_fs_symlink(struct afs_fs_cursor *fc,
const char *name,
const char *contents,
struct afs_status_cb *dvnode_scb,
struct afs_fid *newfid,
- struct afs_status_cb *new_scb)
+ struct afs_status_cb *new_scb,
+ bool is_mountpoint)
{
struct afs_vnode *dvnode = fc->vnode;
struct afs_call *call;
@@ -913,7 +914,7 @@ int afs_fs_symlink(struct afs_fs_cursor *fc,

if (test_bit(AFS_SERVER_FL_IS_YFS, &fc->cbi->server->flags))
return yfs_fs_symlink(fc, name, contents, dvnode_scb,
- newfid, new_scb);
+ newfid, new_scb, is_mountpoint);

_enter("");

@@ -959,7 +960,7 @@ int afs_fs_symlink(struct afs_fs_cursor *fc,
*bp++ = htonl(dvnode->vfs_inode.i_mtime.tv_sec); /* mtime */
*bp++ = 0; /* owner */
*bp++ = 0; /* group */
- *bp++ = htonl(S_IRWXUGO); /* unix mode */
+ *bp++ = htonl(is_mountpoint ? 0644 : S_IRWXUGO); /* unix mode */
*bp++ = 0; /* segment size */

afs_use_fs_server(call, fc->cbi);
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 1d81fc4c3058..70509f2ddd00 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -965,7 +965,8 @@ extern int afs_fs_remove(struct afs_fs_cursor *, struct afs_vnode *, const char
extern int afs_fs_link(struct afs_fs_cursor *, struct afs_vnode *, const char *,
struct afs_status_cb *, struct afs_status_cb *);
extern int afs_fs_symlink(struct afs_fs_cursor *, const char *, const char *,
- struct afs_status_cb *, struct afs_fid *, struct afs_status_cb *);
+ struct afs_status_cb *, struct afs_fid *, struct afs_status_cb *,
+ bool);
extern int afs_fs_rename(struct afs_fs_cursor *, const char *,
struct afs_vnode *, const char *,
struct afs_status_cb *, struct afs_status_cb *);
@@ -1370,7 +1371,8 @@ extern int yfs_fs_remove(struct afs_fs_cursor *, struct afs_vnode *, const char
extern int yfs_fs_link(struct afs_fs_cursor *, struct afs_vnode *, const char *,
struct afs_status_cb *, struct afs_status_cb *);
extern int yfs_fs_symlink(struct afs_fs_cursor *, const char *, const char *,
- struct afs_status_cb *, struct afs_fid *, struct afs_status_cb *);
+ struct afs_status_cb *, struct afs_fid *, struct afs_status_cb *,
+ bool);
extern int yfs_fs_rename(struct afs_fs_cursor *, const char *, struct afs_vnode *, const char *,
struct afs_status_cb *, struct afs_status_cb *);
extern int yfs_fs_store_data(struct afs_fs_cursor *, struct address_space *,
diff --git a/fs/afs/mntpt.c b/fs/afs/mntpt.c
index 79bc5f1338ed..b06ceed9b8f5 100644
--- a/fs/afs/mntpt.c
+++ b/fs/afs/mntpt.c
@@ -17,9 +17,6 @@
#include "internal.h"


-static struct dentry *afs_mntpt_lookup(struct inode *dir,
- struct dentry *dentry,
- unsigned int flags);
static int afs_mntpt_open(struct inode *inode, struct file *file);
static void afs_mntpt_expiry_timed_out(struct work_struct *work);

@@ -29,7 +26,6 @@ const struct file_operations afs_mntpt_file_operations = {
};

const struct inode_operations afs_mntpt_inode_operations = {
- .lookup = afs_mntpt_lookup,
.readlink = page_readlink,
.getattr = afs_getattr,
.listxattr = afs_listxattr,
@@ -46,17 +42,6 @@ static unsigned long afs_mntpt_expiry_timeout = 10 * 60;

static const char afs_root_volume[] = "root.cell";

-/*
- * no valid lookup procedure on this sort of dir
- */
-static struct dentry *afs_mntpt_lookup(struct inode *dir,
- struct dentry *dentry,
- unsigned int flags)
-{
- _enter("%p,%p{%pd2}", dir, dentry, dentry);
- return ERR_PTR(-EREMOTE);
-}
-
/*
* no valid open procedure on this sort of dir
*/
diff --git a/fs/afs/yfsclient.c b/fs/afs/yfsclient.c
index a26126ac7bf1..5ff95d5643f5 100644
--- a/fs/afs/yfsclient.c
+++ b/fs/afs/yfsclient.c
@@ -1080,14 +1080,15 @@ static const struct afs_call_type yfs_RXYFSSymlink = {
};

/*
- * Create a symbolic link.
+ * Create a symbolic link or a mountpoint (differentiated by mode).
*/
int yfs_fs_symlink(struct afs_fs_cursor *fc,
const char *name,
const char *contents,
struct afs_status_cb *dvnode_scb,
struct afs_fid *newfid,
- struct afs_status_cb *vnode_scb)
+ struct afs_status_cb *vnode_scb,
+ bool is_mountpoint)
{
struct afs_vnode *dvnode = fc->vnode;
struct afs_call *call;
@@ -1125,7 +1126,7 @@ int yfs_fs_symlink(struct afs_fs_cursor *fc,
bp = xdr_encode_YFSFid(bp, &dvnode->fid);
bp = xdr_encode_string(bp, name, namesz);
bp = xdr_encode_string(bp, contents, contents_sz);
- bp = xdr_encode_YFSStoreStatus_mode(bp, S_IRWXUGO);
+ bp = xdr_encode_YFSStoreStatus_mode(bp, is_mountpoint ? 0644 : S_IRWXUGO);
yfs_check_req(call, bp);

afs_use_fs_server(call, fc->cbi);