afs: Implement @sys substitution handling

Implement the AFS feature by which @sys at the end of a pathname component
may be substituted for one of a list of values, typically naming the
operating system.  Up to 16 alternatives may be specified and these are
tried in turn until one works.  Each network namespace has[*] a separate
independent list.

Upon creation of a new network namespace, the list of values is
initialised[*] to a single OpenAFS-compatible string representing arch type
plus "_linux26".  For example, on x86_64, the sysname is "amd64_linux26".

[*] Or will, once network namespace support is finalised in kAFS.

The list may be set by:

	# for i in foo bar linux-x86_64; do echo $i; done >/proc/fs/afs/sysname

for which separate writes to the same fd are amalgamated and applied on
close.  The LF character may be used as a separator to specify multiple
items in the same write() call.

The list may be cleared by:

	# echo >/proc/fs/afs/sysname

and read by:

	# cat /proc/fs/afs/sysname
	foo
	bar
	linux-x86_64

Signed-off-by: David Howells <dhowells@redhat.com>
This commit is contained in:
David Howells 2018-04-09 21:12:31 +01:00
parent 5cf9dd55a0
commit 6f8880d8e6
5 changed files with 380 additions and 2 deletions

View File

@ -11,7 +11,7 @@ Contents:
- Proc filesystem. - Proc filesystem.
- The cell database. - The cell database.
- Security. - Security.
- Examples. - The @sys substitution.
======== ========
@ -230,3 +230,29 @@ If a file is opened with a particular key and then the file descriptor is
passed to a process that doesn't have that key (perhaps over an AF_UNIX passed to a process that doesn't have that key (perhaps over an AF_UNIX
socket), then the operations on the file will be made with key that was used to socket), then the operations on the file will be made with key that was used to
open the file. open the file.
=====================
THE @SYS SUBSTITUTION
=====================
The list of up to 16 @sys substitutions for the current network namespace can
be configured by writing a list to /proc/fs/afs/sysname:
[root@andromeda ~]# echo foo amd64_linux_26 >/proc/fs/afs/sysname
or cleared entirely by writing an empty list:
[root@andromeda ~]# echo >/proc/fs/afs/sysname
The current list for current network namespace can be retrieved by:
[root@andromeda ~]# cat /proc/fs/afs/sysname
foo
amd64_linux_26
When @sys is being substituted for, each element of the list is tried in the
order given.
By default, the list will contain one item that conforms to the pattern
"<arch>_linux_26", amd64 being the name for x86_64.

View File

@ -766,6 +766,62 @@ out:
return ERR_PTR(ret); return ERR_PTR(ret);
} }
/*
* Look up an entry in a directory with @sys substitution.
*/
static struct dentry *afs_lookup_atsys(struct inode *dir, struct dentry *dentry,
struct key *key)
{
struct afs_sysnames *subs;
struct afs_net *net = afs_i2net(dir);
struct dentry *ret;
char *buf, *p, *name;
int len, i;
_enter("");
ret = ERR_PTR(-ENOMEM);
p = buf = kmalloc(AFSNAMEMAX, GFP_KERNEL);
if (!buf)
goto out_p;
if (dentry->d_name.len > 4) {
memcpy(p, dentry->d_name.name, dentry->d_name.len - 4);
p += dentry->d_name.len - 4;
}
/* There is an ordered list of substitutes that we have to try. */
read_lock(&net->sysnames_lock);
subs = net->sysnames;
refcount_inc(&subs->usage);
read_unlock(&net->sysnames_lock);
for (i = 0; i < subs->nr; i++) {
name = subs->subs[i];
len = dentry->d_name.len - 4 + strlen(name);
if (len >= AFSNAMEMAX) {
ret = ERR_PTR(-ENAMETOOLONG);
goto out_s;
}
strcpy(p, name);
ret = lookup_one_len(buf, dentry->d_parent, len);
if (IS_ERR(ret) || d_is_positive(ret))
goto out_s;
dput(ret);
}
/* We don't want to d_add() the @sys dentry here as we don't want to
* the cached dentry to hide changes to the sysnames list.
*/
ret = NULL;
out_s:
afs_put_sysnames(subs);
kfree(buf);
out_p:
key_put(key);
return ret;
}
/* /*
* look up an entry in a directory * look up an entry in a directory
*/ */
@ -805,6 +861,13 @@ static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry,
return ERR_PTR(ret); return ERR_PTR(ret);
} }
if (dentry->d_name.len >= 4 &&
dentry->d_name.name[dentry->d_name.len - 4] == '@' &&
dentry->d_name.name[dentry->d_name.len - 3] == 's' &&
dentry->d_name.name[dentry->d_name.len - 2] == 'y' &&
dentry->d_name.name[dentry->d_name.len - 1] == 's')
return afs_lookup_atsys(dir, dentry, key);
inode = afs_do_lookup(dir, dentry, key); inode = afs_do_lookup(dir, dentry, key);
if (IS_ERR(inode)) { if (IS_ERR(inode)) {
ret = PTR_ERR(inode); ret = PTR_ERR(inode);

View File

@ -198,6 +198,18 @@ static inline struct afs_super_info *AFS_FS_S(struct super_block *sb)
extern struct file_system_type afs_fs_type; extern struct file_system_type afs_fs_type;
/*
* Set of substitutes for @sys.
*/
struct afs_sysnames {
#define AFS_NR_SYSNAME 16
char *subs[AFS_NR_SYSNAME];
refcount_t usage;
unsigned short nr;
short error;
char blank[1];
};
/* /*
* AFS network namespace record. * AFS network namespace record.
*/ */
@ -246,8 +258,11 @@ struct afs_net {
/* Misc */ /* Misc */
struct proc_dir_entry *proc_afs; /* /proc/net/afs directory */ struct proc_dir_entry *proc_afs; /* /proc/net/afs directory */
struct afs_sysnames *sysnames;
rwlock_t sysnames_lock;
}; };
extern const char afs_init_sysname[];
extern struct afs_net __afs_net;// Dummy AFS network namespace; TODO: replace with real netns extern struct afs_net __afs_net;// Dummy AFS network namespace; TODO: replace with real netns
enum afs_cell_state { enum afs_cell_state {
@ -789,6 +804,7 @@ extern int __net_init afs_proc_init(struct afs_net *);
extern void __net_exit afs_proc_cleanup(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 int afs_proc_cell_setup(struct afs_net *, struct afs_cell *);
extern void afs_proc_cell_remove(struct afs_net *, struct afs_cell *); extern void afs_proc_cell_remove(struct afs_net *, struct afs_cell *);
extern void afs_put_sysnames(struct afs_sysnames *);
/* /*
* rotate.c * rotate.c

View File

@ -34,11 +34,42 @@ MODULE_PARM_DESC(rootcell, "root AFS cell name and VL server IP addr list");
struct workqueue_struct *afs_wq; struct workqueue_struct *afs_wq;
struct afs_net __afs_net; struct afs_net __afs_net;
#if defined(CONFIG_ALPHA)
const char afs_init_sysname[] = "alpha_linux26";
#elif defined(CONFIG_X86_64)
const char afs_init_sysname[] = "amd64_linux26";
#elif defined(CONFIG_ARM)
const char afs_init_sysname[] = "arm_linux26";
#elif defined(CONFIG_ARM64)
const char afs_init_sysname[] = "aarch64_linux26";
#elif defined(CONFIG_X86_32)
const char afs_init_sysname[] = "i386_linux26";
#elif defined(CONFIG_IA64)
const char afs_init_sysname[] = "ia64_linux26";
#elif defined(CONFIG_PPC64)
const char afs_init_sysname[] = "ppc64_linux26";
#elif defined(CONFIG_PPC32)
const char afs_init_sysname[] = "ppc_linux26";
#elif defined(CONFIG_S390)
#ifdef CONFIG_64BIT
const char afs_init_sysname[] = "s390x_linux26";
#else
const char afs_init_sysname[] = "s390_linux26";
#endif
#elif defined(CONFIG_SPARC64)
const char afs_init_sysname[] = "sparc64_linux26";
#elif defined(CONFIG_SPARC32)
const char afs_init_sysname[] = "sparc_linux26";
#else
const char afs_init_sysname[] = "unknown_linux26";
#endif
/* /*
* Initialise an AFS network namespace record. * 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 afs_net *net)
{ {
struct afs_sysnames *sysnames;
int ret; int ret;
net->live = true; net->live = true;
@ -67,6 +98,16 @@ static int __net_init afs_net_init(struct afs_net *net)
INIT_WORK(&net->fs_manager, afs_manage_servers); INIT_WORK(&net->fs_manager, afs_manage_servers);
timer_setup(&net->fs_timer, afs_servers_timer, 0); timer_setup(&net->fs_timer, afs_servers_timer, 0);
ret = -ENOMEM;
sysnames = kzalloc(sizeof(*sysnames), GFP_KERNEL);
if (!sysnames)
goto error_sysnames;
sysnames->subs[0] = (char *)&afs_init_sysname;
sysnames->nr = 1;
refcount_set(&sysnames->usage, 1);
net->sysnames = sysnames;
rwlock_init(&net->sysnames_lock);
/* Register the /proc stuff */ /* Register the /proc stuff */
ret = afs_proc_init(net); ret = afs_proc_init(net);
if (ret < 0) if (ret < 0)
@ -92,6 +133,8 @@ error_cell_init:
net->live = false; net->live = false;
afs_proc_cleanup(net); afs_proc_cleanup(net);
error_proc: error_proc:
afs_put_sysnames(net->sysnames);
error_sysnames:
net->live = false; net->live = false;
return ret; return ret;
} }
@ -106,6 +149,7 @@ static void __net_exit afs_net_exit(struct afs_net *net)
afs_purge_servers(net); afs_purge_servers(net);
afs_close_socket(net); afs_close_socket(net);
afs_proc_cleanup(net); afs_proc_cleanup(net);
afs_put_sysnames(net->sysnames);
} }
/* /*

View File

@ -126,6 +126,32 @@ static const struct file_operations afs_proc_servers_fops = {
.release = seq_release, .release = seq_release,
}; };
static int afs_proc_sysname_open(struct inode *inode, struct file *file);
static int afs_proc_sysname_release(struct inode *inode, struct file *file);
static void *afs_proc_sysname_start(struct seq_file *p, loff_t *pos);
static void *afs_proc_sysname_next(struct seq_file *p, void *v,
loff_t *pos);
static void afs_proc_sysname_stop(struct seq_file *p, void *v);
static int afs_proc_sysname_show(struct seq_file *m, void *v);
static ssize_t afs_proc_sysname_write(struct file *file,
const char __user *buf,
size_t size, loff_t *_pos);
static const struct seq_operations afs_proc_sysname_ops = {
.start = afs_proc_sysname_start,
.next = afs_proc_sysname_next,
.stop = afs_proc_sysname_stop,
.show = afs_proc_sysname_show,
};
static const struct file_operations afs_proc_sysname_fops = {
.open = afs_proc_sysname_open,
.read = seq_read,
.llseek = seq_lseek,
.release = afs_proc_sysname_release,
.write = afs_proc_sysname_write,
};
/* /*
* initialise the /proc/fs/afs/ directory * initialise the /proc/fs/afs/ directory
*/ */
@ -139,7 +165,8 @@ int afs_proc_init(struct afs_net *net)
if (!proc_create("cells", 0644, net->proc_afs, &afs_proc_cells_fops) || if (!proc_create("cells", 0644, net->proc_afs, &afs_proc_cells_fops) ||
!proc_create("rootcell", 0644, net->proc_afs, &afs_proc_rootcell_fops) || !proc_create("rootcell", 0644, net->proc_afs, &afs_proc_rootcell_fops) ||
!proc_create("servers", 0644, net->proc_afs, &afs_proc_servers_fops)) !proc_create("servers", 0644, net->proc_afs, &afs_proc_servers_fops) ||
!proc_create("sysname", 0644, net->proc_afs, &afs_proc_sysname_fops))
goto error_tree; goto error_tree;
_leave(" = 0"); _leave(" = 0");
@ -330,6 +357,12 @@ static ssize_t afs_proc_rootcell_write(struct file *file,
if (IS_ERR(kbuf)) if (IS_ERR(kbuf))
return PTR_ERR(kbuf); return PTR_ERR(kbuf);
ret = -EINVAL;
if (kbuf[0] == '.')
goto out;
if (memchr(kbuf, '/', size))
goto out;
/* trim to first NL */ /* trim to first NL */
s = memchr(kbuf, '\n', size); s = memchr(kbuf, '\n', size);
if (s) if (s)
@ -342,6 +375,7 @@ static ssize_t afs_proc_rootcell_write(struct file *file,
if (ret >= 0) if (ret >= 0)
ret = size; /* consume everything, always */ ret = size; /* consume everything, always */
out:
kfree(kbuf); kfree(kbuf);
_leave(" = %d", ret); _leave(" = %d", ret);
return ret; return ret;
@ -635,3 +669,198 @@ static int afs_proc_servers_show(struct seq_file *m, void *v)
&alist->addrs[alist->index].transport); &alist->addrs[alist->index].transport);
return 0; return 0;
} }
void afs_put_sysnames(struct afs_sysnames *sysnames)
{
int i;
if (sysnames && refcount_dec_and_test(&sysnames->usage)) {
for (i = 0; i < sysnames->nr; i++)
if (sysnames->subs[i] != afs_init_sysname &&
sysnames->subs[i] != sysnames->blank)
kfree(sysnames->subs[i]);
}
}
/*
* Handle opening of /proc/fs/afs/sysname. If it is opened for writing, we
* assume the caller wants to change the substitution list and we allocate a
* buffer to hold the list.
*/
static int afs_proc_sysname_open(struct inode *inode, struct file *file)
{
struct afs_sysnames *sysnames;
struct seq_file *m;
int ret;
ret = seq_open(file, &afs_proc_sysname_ops);
if (ret < 0)
return ret;
if (file->f_mode & FMODE_WRITE) {
sysnames = kzalloc(sizeof(*sysnames), GFP_KERNEL);
if (!sysnames) {
seq_release(inode, file);
return -ENOMEM;
}
refcount_set(&sysnames->usage, 1);
m = file->private_data;
m->private = sysnames;
}
return 0;
}
/*
* Handle writes to /proc/fs/afs/sysname to set the @sys substitution.
*/
static ssize_t afs_proc_sysname_write(struct file *file,
const char __user *buf,
size_t size, loff_t *_pos)
{
struct afs_sysnames *sysnames;
struct seq_file *m = file->private_data;
char *kbuf = NULL, *s, *p, *sub;
int ret, len;
sysnames = m->private;
if (!sysnames)
return -EINVAL;
if (sysnames->error)
return sysnames->error;
if (size >= PAGE_SIZE - 1) {
sysnames->error = -EINVAL;
return -EINVAL;
}
if (size == 0)
return 0;
kbuf = memdup_user_nul(buf, size);
if (IS_ERR(kbuf))
return PTR_ERR(kbuf);
inode_lock(file_inode(file));
p = kbuf;
while ((s = strsep(&p, " \t\n"))) {
len = strlen(s);
if (len == 0)
continue;
ret = -ENAMETOOLONG;
if (len >= AFSNAMEMAX)
goto error;
if (len >= 4 &&
s[len - 4] == '@' &&
s[len - 3] == 's' &&
s[len - 2] == 'y' &&
s[len - 1] == 's')
/* Protect against recursion */
goto invalid;
if (s[0] == '.' &&
(len < 2 || (len == 2 && s[1] == '.')))
goto invalid;
if (memchr(s, '/', len))
goto invalid;
ret = -EFBIG;
if (sysnames->nr >= AFS_NR_SYSNAME)
goto out;
if (strcmp(s, afs_init_sysname) == 0) {
sub = (char *)afs_init_sysname;
} else {
ret = -ENOMEM;
sub = kmemdup(s, len + 1, GFP_KERNEL);
if (!sub)
goto out;
}
sysnames->subs[sysnames->nr] = sub;
sysnames->nr++;
}
ret = size; /* consume everything, always */
out:
inode_unlock(file_inode(file));
kfree(kbuf);
return ret;
invalid:
ret = -EINVAL;
error:
sysnames->error = ret;
goto out;
}
static int afs_proc_sysname_release(struct inode *inode, struct file *file)
{
struct afs_sysnames *sysnames, *kill = NULL;
struct seq_file *m = file->private_data;
struct afs_net *net = afs_seq2net(m);
sysnames = m->private;
if (sysnames) {
if (!sysnames->error) {
kill = sysnames;
if (sysnames->nr == 0) {
sysnames->subs[0] = sysnames->blank;
sysnames->nr++;
}
write_lock(&net->sysnames_lock);
kill = net->sysnames;
net->sysnames = sysnames;
write_unlock(&net->sysnames_lock);
}
afs_put_sysnames(kill);
}
return seq_release(inode, file);
}
static void *afs_proc_sysname_start(struct seq_file *m, loff_t *pos)
__acquires(&net->sysnames_lock)
{
struct afs_net *net = afs_seq2net(m);
struct afs_sysnames *names = net->sysnames;
read_lock(&net->sysnames_lock);
if (*pos >= names->nr)
return NULL;
return (void *)(unsigned long)(*pos + 1);
}
static void *afs_proc_sysname_next(struct seq_file *m, void *v, loff_t *pos)
{
struct afs_net *net = afs_seq2net(m);
struct afs_sysnames *names = net->sysnames;
*pos += 1;
if (*pos >= names->nr)
return NULL;
return (void *)(unsigned long)(*pos + 1);
}
static void afs_proc_sysname_stop(struct seq_file *m, void *v)
__releases(&net->sysnames_lock)
{
struct afs_net *net = afs_seq2net(m);
read_unlock(&net->sysnames_lock);
}
static int afs_proc_sysname_show(struct seq_file *m, void *v)
{
struct afs_net *net = afs_seq2net(m);
struct afs_sysnames *sysnames = net->sysnames;
unsigned int i = (unsigned long)v - 1;
if (i < sysnames->nr)
seq_printf(m, "%s\n", sysnames->subs[i]);
return 0;
}