From: Kirill Tkhai <ktkhai@virtuozzo.com> To: viro@zeniv.linux.org.uk, adobriyan@gmail.com, davem@davemloft.net, ebiederm@xmission.com, akpm@linux-foundation.org, christian.brauner@ubuntu.com, areber@redhat.com, serge@hallyn.com, linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, ktkhai@virtuozzo.com Subject: [PATCH 11/23] fs: Add /proc/namespaces/ directory Date: Thu, 30 Jul 2020 15:00:19 +0300 Message-ID: <159611041929.535980.14513096920129728440.stgit@localhost.localdomain> (raw) In-Reply-To: <159611007271.535980.15362304262237658692.stgit@localhost.localdomain> This is a new directory to show all namespaces, which can be accessed from this /proc tasks credentials. Every /proc is related to a pid_namespace, and the pid_namespace is related to a user_namespace. The items, we show in this /proc/namespaces/ directory, are the namespaces, whose user_namespaces are the same as /proc's user_namespace, or their descendants. Say, /proc has pid_ns->user_ns, so in /proc/namespace we show only a ns, which is in_userns(pid_ns->user_ns, ns->user_ns). The final result is like below: # ls /proc/namespaces/ -l lrwxrwxrwx 1 root root 0 Jul 29 16:50 'cgroup:[4026531835]' -> 'cgroup:[4026531835]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'ipc:[4026531839]' -> 'ipc:[4026531839]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'mnt:[4026531840]' -> 'mnt:[4026531840]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'mnt:[4026531861]' -> 'mnt:[4026531861]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'mnt:[4026532133]' -> 'mnt:[4026532133]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'mnt:[4026532134]' -> 'mnt:[4026532134]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'mnt:[4026532135]' -> 'mnt:[4026532135]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'mnt:[4026532136]' -> 'mnt:[4026532136]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'net:[4026531993]' -> 'net:[4026531993]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'pid:[4026531836]' -> 'pid:[4026531836]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'time:[4026531834]' -> 'time:[4026531834]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'user:[4026531837]' -> 'user:[4026531837]' lrwxrwxrwx 1 root root 0 Jul 29 16:50 'uts:[4026531838]' -> 'uts:[4026531838]' Every namespace may be open like ordinary file in /proc/[pid]/ns. Signed-off-by: Kirill Tkhai <ktkhai@virtuozzo.com> --- fs/nsfs.c | 2 fs/proc/Makefile | 1 fs/proc/internal.h | 16 ++ fs/proc/namespaces.c | 314 +++++++++++++++++++++++++++++++++++++++++++++++ fs/proc/root.c | 17 ++- include/linux/proc_fs.h | 1 6 files changed, 345 insertions(+), 6 deletions(-) create mode 100644 fs/proc/namespaces.c diff --git a/fs/nsfs.c b/fs/nsfs.c index ee4be67d3a0b..61b789d2089c 100644 --- a/fs/nsfs.c +++ b/fs/nsfs.c @@ -58,7 +58,7 @@ static void nsfs_evict(struct inode *inode) ns->ops->put(ns); } -static int __ns_get_path(struct path *path, struct ns_common *ns) +int __ns_get_path(struct path *path, struct ns_common *ns) { struct vfsmount *mnt = nsfs_mnt; struct dentry *dentry; diff --git a/fs/proc/Makefile b/fs/proc/Makefile index dc2d51f42905..34ff671c6d59 100644 --- a/fs/proc/Makefile +++ b/fs/proc/Makefile @@ -25,6 +25,7 @@ proc-y += util.o proc-y += version.o proc-y += softirqs.o proc-y += task_namespaces.o +proc-y += namespaces.o proc-y += self.o proc-y += thread_self.o proc-$(CONFIG_PROC_SYSCTL) += proc_sysctl.o diff --git a/fs/proc/internal.h b/fs/proc/internal.h index 572757ff97be..d19fe5574799 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -134,10 +134,11 @@ void task_dump_owner(struct task_struct *task, umode_t mode, kuid_t *ruid, kgid_t *rgid); unsigned name_to_int(const struct qstr *qstr); -/* - * Offset of the first process in the /proc root directory.. - */ -#define FIRST_PROCESS_ENTRY 256 + +/* Offset of "namespaces" entry in /proc root directory */ +#define NAMESPACES_ENTRY 256 +/* Offset of the first process in the /proc root directory */ +#define FIRST_PROCESS_ENTRY (NAMESPACES_ENTRY + 1) /* Worst case buffer size needed for holding an integer. */ #define PROC_NUMBUF 13 @@ -168,6 +169,7 @@ extern void proc_pid_evict_inode(struct proc_inode *); extern struct inode *proc_pid_make_inode(struct super_block *, struct task_struct *, umode_t); extern void pid_update_inode(struct task_struct *, struct inode *); extern int pid_delete_dentry(const struct dentry *); +extern int proc_emit_namespaces(struct file *, struct dir_context *); extern int proc_pid_readdir(struct file *, struct dir_context *); struct dentry *proc_pid_lookup(struct dentry *, unsigned int); extern loff_t mem_lseek(struct file *, loff_t, int); @@ -222,6 +224,12 @@ void set_proc_pid_nlink(void); extern struct inode *proc_get_inode(struct super_block *, struct proc_dir_entry *); extern void proc_entry_rundown(struct proc_dir_entry *); +/* + * namespaces.c + */ +extern int proc_setup_namespaces(struct super_block *); +extern void proc_namespaces_init(void); + /* * task_namespaces.c */ diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c new file mode 100644 index 000000000000..ab47e1555619 --- /dev/null +++ b/fs/proc/namespaces.c @@ -0,0 +1,314 @@ +#include <linux/pid_namespace.h> +#include <linux/user_namespace.h> +#include <linux/namei.h> +#include "internal.h" + +static unsigned namespaces_inum __ro_after_init; + +int proc_emit_namespaces(struct file *file, struct dir_context *ctx) +{ + struct proc_fs_info *fs_info = proc_sb_info(file_inode(file)->i_sb); + struct inode *inode = d_inode(fs_info->proc_namespaces); + + return dir_emit(ctx, "namespaces", 10, inode->i_ino, DT_DIR); +} + +static int parse_namespace_dentry_name(const struct dentry *dentry, + const char **type, unsigned int *type_len, unsigned int *inum) +{ + const char *p, *name; + int count; + + *type = name = dentry->d_name.name; + p = strchr(name, ':'); + *type_len = p - name; + if (!p || p == name) + return -ENOENT; + + p += 1; + if (sscanf(p, "[%u]%n", inum, &count) != 1 || *(p + count) != '\0' || + *inum < PROC_NS_MIN_INO) + return -ENOENT; + + return 0; +} + +static struct ns_common *get_namespace_by_dentry(struct pid_namespace *pid_ns, + const struct dentry *dentry) +{ + unsigned int type_len, inum, p_inum; + struct user_namespace *user_ns; + struct ns_common *ns; + const char *type; + + if (parse_namespace_dentry_name(dentry, &type, &type_len, &inum) < 0) + return NULL; + + p_inum = inum - 1; + ns = ns_get_next(&p_inum); + if (!ns) + return NULL; + + if (ns->inum != inum || strncmp(type, ns->ops->name, type_len) != 0 || + ns->ops->name[type_len] != '\0') { + ns->ops->put(ns); + return NULL; + } + + if (ns->ops != &userns_operations) + user_ns = ns->ops->owner(ns); + else + user_ns = container_of(ns, struct user_namespace, ns); + + if (!in_userns(pid_ns->user_ns, user_ns)) { + ns->ops->put(ns); + return NULL; + } + + return ns; +} + +static struct dentry *proc_namespace_instantiate(struct dentry *dentry, + struct task_struct *task, const void *ptr); + +static struct dentry *proc_namespaces_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + struct pid_namespace *pid_ns = proc_pid_ns(dir->i_sb); + struct task_struct *task; + struct ns_common *ns; + + ns = get_namespace_by_dentry(pid_ns, dentry); + if (!ns) + return ERR_PTR(-ENOENT); + + read_lock(&tasklist_lock); + task = get_task_struct(pid_ns->child_reaper); + read_unlock(&tasklist_lock); + + dentry = proc_namespace_instantiate(dentry, task, ns); + put_task_struct(task); + ns->ops->put(ns); + + return dentry; +} + +static int proc_namespaces_permission(struct inode *inode, int mask) +{ + if ((mask & MAY_EXEC) && S_ISLNK(inode->i_mode)) + return -EACCES; + + return 0; +} + +static int proc_namespaces_getattr(const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int query_flags) +{ + struct inode *inode = d_inode(path->dentry); + + generic_fillattr(inode, stat); + return 0; +} + +static const struct inode_operations proc_namespaces_inode_operations = { + .lookup = proc_namespaces_lookup, + .permission = proc_namespaces_permission, + .getattr = proc_namespaces_getattr, +}; + +static int proc_namespaces_readlink(struct dentry *dentry, char __user *buffer, int buflen) +{ + struct inode *dir = dentry->d_parent->d_inode; + struct pid_namespace *pid_ns = proc_pid_ns(dir->i_sb); + struct ns_common *ns; + + ns = get_namespace_by_dentry(pid_ns, dentry); + if (!ns) + return -ENOENT; + ns->ops->put(ns); + + /* proc_namespaces_readdir() creates dentry names in namespace format */ + return readlink_copy(buffer, buflen, dentry->d_iname); +} + +int __ns_get_path(struct path *path, struct ns_common *ns); + +static const char *proc_namespaces_getlink(struct dentry *dentry, + struct inode *inode, struct delayed_call *done) +{ + struct pid_namespace *pid_ns = proc_pid_ns(inode->i_sb); + struct ns_common *ns; + struct path path; + int ret; + + if (!dentry) + return ERR_PTR(-ECHILD); + + while (1) { + ret = -ENOENT; + ns = get_namespace_by_dentry(pid_ns, dentry); + if (!ns) + goto out; + + ret = __ns_get_path(&path, ns); + if (ret == -EAGAIN) + continue; + if (ret) + goto out; + break; + } + + ret = nd_jump_link(&path); +out: + return ERR_PTR(ret); +} + +static const struct inode_operations proc_namespaces_link_inode_operations = { + .readlink = proc_namespaces_readlink, + .get_link = proc_namespaces_getlink, +}; + +static int namespace_delete_dentry(const struct dentry *dentry) +{ + struct inode *dir = dentry->d_parent->d_inode; + struct pid_namespace *pid_ns = proc_pid_ns(dir->i_sb); + struct ns_common *ns; + + ns = get_namespace_by_dentry(pid_ns, dentry); + if (!ns) + return 1; + + ns->ops->put(ns); + return 0; +} + +const struct dentry_operations namespaces_dentry_operations = { + .d_delete = namespace_delete_dentry, +}; + +static void namespace_update_inode(struct inode *inode) +{ + struct user_namespace *user_ns = proc_pid_ns(inode->i_sb)->user_ns; + + inode->i_uid = make_kuid(user_ns, 0); + if (!uid_valid(inode->i_uid)) + inode->i_uid = GLOBAL_ROOT_UID; + + inode->i_gid = make_kgid(user_ns, 0); + if (!gid_valid(inode->i_gid)) + inode->i_gid = GLOBAL_ROOT_GID; +} + +static struct dentry *proc_namespace_instantiate(struct dentry *dentry, + struct task_struct *task, const void *ptr) +{ + const struct ns_common *ns = ptr; + struct inode *inode; + struct proc_inode *ei; + + /* + * Create inode with credentials of @task, and add it to @task's + * quick removal list. + */ + inode = proc_pid_make_inode(dentry->d_sb, task, S_IFLNK | S_IRWXUGO); + if (!inode) + return ERR_PTR(-ENOENT); + + ei = PROC_I(inode); + inode->i_op = &proc_namespaces_link_inode_operations; + ei->ns_ops = ns->ops; + namespace_update_inode(inode); + + d_set_d_op(dentry, &namespaces_dentry_operations); + return d_splice_alias(inode, dentry); +} + +static int proc_namespaces_readdir(struct file *file, struct dir_context *ctx) +{ + struct pid_namespace *pid_ns = proc_pid_ns(file_inode(file)->i_sb); + struct user_namespace *user_ns; + struct task_struct *task; + struct ns_common *ns; + unsigned int inum; + + read_lock(&tasklist_lock); + task = get_task_struct(pid_ns->child_reaper); + read_unlock(&tasklist_lock); + + if (!dir_emit_dots(file, ctx)) + goto out; + + inum = ctx->pos - 2; + while ((ns = ns_get_next(&inum)) != NULL) { + unsigned int len; + char name[32]; + + if (ns->ops != &userns_operations) + user_ns = ns->ops->owner(ns); + else + user_ns = container_of(ns, struct user_namespace, ns); + + if (!in_userns(pid_ns->user_ns, user_ns)) + goto next; + + len = snprintf(name, sizeof(name), "%s:[%u]", ns->ops->name, inum); + + if (!proc_fill_cache(file, ctx, name, len, + proc_namespace_instantiate, task, ns)) { + ns->ops->put(ns); + break; + } +next: + ns->ops->put(ns); + ctx->pos = inum + 2; + } +out: + put_task_struct(task); + return 0; +} + +static const struct file_operations proc_namespaces_file_operations = { + .read = generic_read_dir, + .iterate_shared = proc_namespaces_readdir, + .llseek = generic_file_llseek, +}; + +int proc_setup_namespaces(struct super_block *s) +{ + struct proc_fs_info *fs_info = proc_sb_info(s); + struct inode *root_inode = d_inode(s->s_root); + struct dentry *namespaces; + int ret = -ENOMEM; + + inode_lock(root_inode); + namespaces = d_alloc_name(s->s_root, "namespaces"); + if (namespaces) { + struct inode *inode = new_inode_pseudo(s); + if (inode) { + inode->i_ino = namespaces_inum; + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); + inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO; + inode->i_uid = GLOBAL_ROOT_UID; + inode->i_gid = GLOBAL_ROOT_GID; + inode->i_op = &proc_namespaces_inode_operations; + inode->i_fop = &proc_namespaces_file_operations; + d_add(namespaces, inode); + ret = 0; + } else { + dput(namespaces); + } + } + inode_unlock(root_inode); + + if (ret) + pr_err("proc_setup_namespaces: can't allocate /proc/namespaces\n"); + else + fs_info->proc_namespaces = namespaces; + + return ret; +} + +void __init proc_namespaces_init(void) +{ + proc_alloc_inum(&namespaces_inum); +} diff --git a/fs/proc/root.c b/fs/proc/root.c index 5e444d4f9717..e4e4f90fca3d 100644 --- a/fs/proc/root.c +++ b/fs/proc/root.c @@ -206,6 +206,10 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc) return -ENOMEM; } + ret = proc_setup_namespaces(s); + if (ret) + return ret; + ret = proc_setup_self(s); if (ret) { return ret; @@ -272,6 +276,9 @@ static void proc_kill_sb(struct super_block *sb) dput(fs_info->proc_self); dput(fs_info->proc_thread_self); + if (fs_info->proc_namespaces) + dput(fs_info->proc_namespaces); + kill_anon_super(sb); put_pid_ns(fs_info->pid_ns); kfree(fs_info); @@ -289,6 +296,7 @@ void __init proc_root_init(void) { proc_init_kmemcache(); set_proc_pid_nlink(); + proc_namespaces_init(); proc_self_init(); proc_thread_self_init(); proc_symlink("mounts", NULL, "self/mounts"); @@ -326,8 +334,15 @@ static struct dentry *proc_root_lookup(struct inode * dir, struct dentry * dentr static int proc_root_readdir(struct file *file, struct dir_context *ctx) { - if (ctx->pos < FIRST_PROCESS_ENTRY) { + if (ctx->pos < NAMESPACES_ENTRY) { int error = proc_readdir(file, ctx); + if (unlikely(error <= 0)) + return error; + ctx->pos = NAMESPACES_ENTRY; + } + + if (ctx->pos == NAMESPACES_ENTRY) { + int error = proc_emit_namespaces(file, ctx); if (unlikely(error <= 0)) return error; ctx->pos = FIRST_PROCESS_ENTRY; diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h index 97b3f5f06db9..8b0002a6cacf 100644 --- a/include/linux/proc_fs.h +++ b/include/linux/proc_fs.h @@ -61,6 +61,7 @@ struct proc_fs_info { struct pid_namespace *pid_ns; struct dentry *proc_self; /* For /proc/self */ struct dentry *proc_thread_self; /* For /proc/thread-self */ + struct dentry *proc_namespaces; /* For /proc/namespaces */ kgid_t pid_gid; enum proc_hidepid hide_pid; enum proc_pidonly pidonly;
next prev parent reply other threads:[~2020-07-30 12:00 UTC|newest] Thread overview: 76+ messages / expand[flat|nested] mbox.gz Atom feed top 2020-07-30 11:59 [PATCH 00/23] proc: Introduce /proc/namespaces/ directory to expose namespaces lineary Kirill Tkhai 2020-07-30 11:59 ` [PATCH 01/23] ns: Add common refcount into ns_common add use it as counter for net_ns Kirill Tkhai 2020-07-30 13:35 ` Christian Brauner 2020-07-30 14:07 ` Kirill Tkhai 2020-07-30 15:59 ` Christian Brauner 2020-07-30 14:30 ` Christian Brauner 2020-07-30 14:34 ` Kirill Tkhai 2020-07-30 14:39 ` Christian Brauner 2020-07-30 11:59 ` [PATCH 02/23] uts: Use generic ns_common::count Kirill Tkhai 2020-07-30 14:30 ` Christian Brauner 2020-07-30 11:59 ` [PATCH 03/23] ipc: " Kirill Tkhai 2020-07-30 14:32 ` Christian Brauner 2020-07-30 11:59 ` [PATCH 04/23] pid: " Kirill Tkhai 2020-07-30 14:37 ` Christian Brauner 2020-07-30 11:59 ` [PATCH 05/23] user: " Kirill Tkhai 2020-07-30 14:46 ` Christian Brauner 2020-07-30 11:59 ` [PATCH 06/23] mnt: " Kirill Tkhai 2020-07-30 14:49 ` Christian Brauner 2020-07-30 11:59 ` [PATCH 07/23] cgroup: " Kirill Tkhai 2020-07-30 14:50 ` Christian Brauner 2020-07-30 12:00 ` [PATCH 08/23] time: " Kirill Tkhai 2020-07-30 14:52 ` Christian Brauner 2020-07-30 12:00 ` [PATCH 09/23] ns: Introduce ns_idr to be able to iterate all allocated namespaces in the system Kirill Tkhai 2020-07-30 12:23 ` Matthew Wilcox 2020-07-30 13:32 ` Kirill Tkhai 2020-07-30 13:56 ` Matthew Wilcox 2020-07-30 14:12 ` Kirill Tkhai 2020-07-30 14:15 ` Matthew Wilcox 2020-07-30 14:20 ` Kirill Tkhai 2020-07-30 12:00 ` [PATCH 10/23] fs: Rename fs/proc/namespaces.c into fs/proc/task_namespaces.c Kirill Tkhai 2020-07-30 12:00 ` Kirill Tkhai [this message] 2020-07-30 12:18 ` [PATCH 11/23] fs: Add /proc/namespaces/ directory Alexey Dobriyan 2020-07-30 13:22 ` Kirill Tkhai 2020-07-30 13:26 ` Christian Brauner 2020-07-30 14:30 ` Kirill Tkhai 2020-07-30 20:47 ` kernel test robot 2020-07-30 22:20 ` kernel test robot 2020-08-05 8:17 ` kernel test robot 2020-08-05 8:17 ` [RFC PATCH] fs: namespaces_dentry_operations can be static kernel test robot 2020-07-30 12:00 ` [PATCH 12/23] user: Free user_ns one RCU grace period after final counter put Kirill Tkhai 2020-07-30 12:00 ` [PATCH 13/23] user: Add user namespaces into ns_idr Kirill Tkhai 2020-07-30 12:00 ` [PATCH 14/23] net: Add net " Kirill Tkhai 2020-07-30 12:00 ` [PATCH 15/23] pid: Eextract child_reaper check from pidns_for_children_get() Kirill Tkhai 2020-07-30 12:00 ` [PATCH 16/23] proc_ns_operations: Add can_get method Kirill Tkhai 2020-07-30 12:00 ` [PATCH 17/23] pid: Add pid namespaces into ns_idr Kirill Tkhai 2020-07-30 12:00 ` [PATCH 18/23] uts: Free uts namespace one RCU grace period after final counter put Kirill Tkhai 2020-07-30 12:01 ` [PATCH 19/23] uts: Add uts namespaces into ns_idr Kirill Tkhai 2020-07-30 12:01 ` [PATCH 20/23] ipc: Add ipc " Kirill Tkhai 2020-07-30 12:01 ` [PATCH 21/23] mnt: Add mount " Kirill Tkhai 2020-07-30 12:01 ` [PATCH 22/23] cgroup: Add cgroup " Kirill Tkhai 2020-07-30 12:01 ` [PATCH 23/23] time: Add time " Kirill Tkhai 2020-07-30 13:08 ` [PATCH 00/23] proc: Introduce /proc/namespaces/ directory to expose namespaces lineary Christian Brauner 2020-07-30 13:38 ` Christian Brauner 2020-07-30 14:34 ` Eric W. Biederman 2020-07-30 14:42 ` Christian Brauner 2020-07-30 15:01 ` Kirill Tkhai 2020-07-30 22:13 ` Eric W. Biederman 2020-07-31 8:48 ` Pavel Tikhomirov 2020-08-03 10:03 ` Kirill Tkhai 2020-08-03 10:51 ` Alexey Dobriyan 2020-08-06 8:05 ` Andrei Vagin 2020-08-07 8:47 ` Kirill Tkhai 2020-08-10 17:34 ` Andrei Vagin 2020-08-11 10:23 ` Kirill Tkhai 2020-08-12 17:53 ` Andrei Vagin 2020-08-13 8:12 ` Kirill Tkhai 2020-08-14 1:16 ` Andrei Vagin 2020-08-14 15:11 ` Kirill Tkhai 2020-08-14 19:21 ` Andrei Vagin 2020-08-17 14:05 ` Kirill Tkhai 2020-08-17 15:48 ` Eric W. Biederman 2020-08-17 17:47 ` Christian Brauner 2020-08-17 18:53 ` Eric W. Biederman 2020-08-04 5:43 ` Andrei Vagin 2020-08-04 12:11 ` Pavel Tikhomirov 2020-08-04 14:47 ` Kirill Tkhai
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=159611041929.535980.14513096920129728440.stgit@localhost.localdomain \ --to=ktkhai@virtuozzo.com \ --cc=adobriyan@gmail.com \ --cc=akpm@linux-foundation.org \ --cc=areber@redhat.com \ --cc=christian.brauner@ubuntu.com \ --cc=davem@davemloft.net \ --cc=ebiederm@xmission.com \ --cc=linux-fsdevel@vger.kernel.org \ --cc=linux-kernel@vger.kernel.org \ --cc=serge@hallyn.com \ --cc=viro@zeniv.linux.org.uk \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Unnamed repository; edit this file 'description' to name the repository. This inbox may be cloned and mirrored by anyone: git clone --mirror http://archive.lwn.net:8080/linux-fsdevel/0 linux-fsdevel/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 linux-fsdevel linux-fsdevel/ http://archive.lwn.net:8080/linux-fsdevel \ linux-fsdevel@vger.kernel.org lwn-linux-fsdevel@archive.lwn.net public-inbox-index linux-fsdevel Example config snippet for mirrors. Newsgroup available over NNTP: nntp://archive.lwn.net/lwn.kernel.linux-fsdevel AGPL code for this site: git clone https://public-inbox.org/public-inbox.git