All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Darrick J. Wong" <djwong@kernel.org>
To: Theodore Ts'o <tytso@mit.edu>
Cc: Ext4 Developers List <linux-ext4@vger.kernel.org>
Subject: Re: [PATCH 3/3] tune2fs: try to use the SET_TUNE_SB_PARAM ioctl on mounted file systems
Date: Thu, 18 Sep 2025 10:47:24 -0700	[thread overview]
Message-ID: <20250918174724.GH8084@frogsfrogsfrogs> (raw)
In-Reply-To: <20250917032814.395887-4-tytso@mit.edu>

On Tue, Sep 16, 2025 at 11:28:14PM -0400, Theodore Ts'o wrote:
> Try to uuse the new EXT4_IOC_GET_TUNE_SB_PARAM ioctl to update the
> superblock if the file system is mounted.
> 
> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
> ---
>  misc/tune2fs.c | 352 +++++++++++++++++++++++++++++++++++++------------
>  1 file changed, 267 insertions(+), 85 deletions(-)
> 
> diff --git a/misc/tune2fs.c b/misc/tune2fs.c
> index e752c328..b1ec3991 100644
> --- a/misc/tune2fs.c
> +++ b/misc/tune2fs.c
> @@ -101,6 +101,64 @@ struct fsuuid {
>  #define EXT4_IOC_SETFSUUID	_IOW('f', 44, struct fsuuid)
>  #endif
>  
> +#if (!defined(EXT4_IOC_GET_TUNE_SB_PARAM) && defined(__linux__))
> +
> +struct ext4_tune_sb_params {
> +	__u32 set_flags;
> +	__u32 checkinterval;
> +	__u16 errors_behavior;
> +	__u16 mnt_count;
> +	__u16 max_mnt_count;
> +	__u16 raid_stride;
> +	__u64 last_check_time;
> +	__u64 reserved_blocks;
> +	__u64 blocks_count;
> +	__u32 default_mnt_opts;
> +	__u32 reserved_uid;
> +	__u32 reserved_gid;
> +	__u32 raid_stripe_width;
> +	__u16 encoding;
> +	__u16 encoding_flags;
> +	__u8  def_hash_alg;
> +	__u8  pad_1;
> +	__u16 pad_2;
> +	__u32 feature_compat;
> +	__u32 feature_incompat;
> +	__u32 feature_ro_compat;
> +	__u32 set_feature_compat_mask;
> +	__u32 set_feature_incompat_mask;
> +	__u32 set_feature_ro_compat_mask;
> +	__u32 clear_feature_compat_mask;
> +	__u32 clear_feature_incompat_mask;
> +	__u32 clear_feature_ro_compat_mask;
> +	__u8  mount_opts[64];
> +	__u8  pad[64];
> +};
> +
> +#define EXT4_TUNE_FL_ERRORS_BEHAVIOR	0x00000001
> +#define EXT4_TUNE_FL_MNT_COUNT		0x00000002
> +#define EXT4_TUNE_FL_MAX_MNT_COUNT	0x00000004
> +#define EXT4_TUNE_FL_CHECKINTRVAL	0x00000008
> +#define EXT4_TUNE_FL_LAST_CHECK_TIME	0x00000010
> +#define EXT4_TUNE_FL_RESERVED_BLOCKS	0x00000020
> +#define EXT4_TUNE_FL_RESERVED_UID	0x00000040
> +#define EXT4_TUNE_FL_RESERVED_GID	0x00000080
> +#define EXT4_TUNE_FL_DEFAULT_MNT_OPTS	0x00000100
> +#define EXT4_TUNE_FL_DEF_HASH_ALG	0x00000200
> +#define EXT4_TUNE_FL_RAID_STRIDE	0x00000400
> +#define EXT4_TUNE_FL_RAID_STRIPE_WIDTH	0x00000800
> +#define EXT4_TUNE_FL_MOUNT_OPTS		0x00001000
> +#define EXT4_TUNE_FL_FEATURES		0x00002000
> +#define EXT4_TUNE_FL_EDIT_FEATURES	0x00004000
> +#define EXT4_TUNE_FL_FORCE_FSCK		0x00008000
> +#define EXT4_TUNE_FL_ENCODING		0x00010000
> +#define EXT4_TUNE_FL_ENCODING_FLAGS	0x00020000
> +
> +#define EXT4_IOC_GET_TUNE_SB_PARAM	_IOR('f', 45, struct ext4_tune_sb_params)
> +#define EXT4_IOC_SET_TUNE_SB_PARAM	_IOW('f', 46, struct ext4_tune_sb_params)
> +
> +#endif
> +
>  extern int ask_yn(const char *string, int def);
>  
>  #define OPT_MAX_MOUNTCOUNT	 1
> @@ -145,6 +203,8 @@ char *io_options;
>  static int force, do_list_super, sparse_value = -1;
>  static time_t last_check_time;
>  static int max_mount_count, mount_count, mount_flags;
> +static int fs_fd = -1;
> +static char mntpt[PATH_MAX + 1];
>  static unsigned long interval;
>  static blk64_t reserved_blocks;
>  static double reserved_ratio;
> @@ -2052,6 +2112,10 @@ static void parse_tune2fs_options(int argc, char **argv)
>  			}
>  			if (max_mount_count == 0)
>  				max_mount_count = -1;
> +			else if (max_mount_count == 65536) {
> +				max_mount_count = EXT2_DFL_MAX_MNT_COUNT +
> +					(random() % EXT2_DFL_MAX_MNT_COUNT);
> +			}
>  			break;
>  		case 'C':
>  			opts[OPT_MOUNTCOUNT] = true;
> @@ -2134,6 +2198,12 @@ static void parse_tune2fs_options(int argc, char **argv)
>  					_("bad interval - %s"), optarg);
>  				usage();
>  			}
> +			if ((unsigned long long)interval >= (1ULL << 32)) {
> +				com_err(program_name, 0,
> +					_("interval between checks is too big (%lu)"),
> +					interval);
> +				exit(1);
> +			}
>  
> -/*
> - * Use FS_IOC_SETFSLABEL or FS_IOC_GETFSLABEL to set/get file system label
> - * Return:	0 on success
> - *		1 on error
> - *		-1 when the old method should be used
> - */
> -static int handle_fslabel(int setlabel)
> +static int get_mount_flags()
> +{
> +	errcode_t	ret;
> +
> +	ret = ext2fs_check_mount_point(device_name, &mount_flags,
> +				       mntpt, sizeof(mntpt));
> +	if (ret) {
> +		com_err("ext2fs_check_mount_point", ret,
> +			_("while determining whether %s is mounted."),
> +			device_name);
> +		return -1;
> +	}
> +
> +#ifdef __linux__
> +	if ((ret == 0) &&
> +	    (mount_flags & EXT2_MF_MOUNTED) &&
> +	    mntpt[0])
> +		fs_fd = open(mntpt, O_RDONLY);
> +#endif
> +	return 0;
> +}
> +
> +static int try_mounted_tune2fs()
>  {
>  #ifdef __linux__
>  	errcode_t ret;
> -	int mnt_flags, fd;
>  	char label[FSLABEL_MAX];
> -	unsigned int maxlen = FSLABEL_MAX - 1;
> -	char mntpt[PATH_MAX + 1];
> +	struct ext4_tune_sb_params params;
> +	__u64 fs_blocks_count;
> +	__u32 fs_feature_array[3], kernel_set_mask[3], kernel_clear_mask[3];
> +	__u32 default_mnt_opts;
> +	int fs_set_ops = 0;
>  
> -	ret = ext2fs_check_mount_point(device_name, &mnt_flags,
> -					  mntpt, sizeof(mntpt));
> -	if (ret)
> -		return -1;
> +	if (fs_fd < 0)
> +		return 0;
>  
> -	if (!(mnt_flags & EXT2_MF_MOUNTED) ||
> -	    (setlabel && (mnt_flags & EXT2_MF_READONLY)))
> -		return -1;
> +	if (opts[OPT_PRINT_LABEL] &&
> +	    !ioctl(fs_fd, FS_IOC_GETFSLABEL, &label)) {
> +		printf("%.*s\n", EXT2_LEN_STR(label));
> +		opts[OPT_PRINT_LABEL] = false;
> +	}
>  
> -	if (!mntpt[0])
> -		return -1;
> +	if (mount_flags & EXT2_MF_READONLY)
> +		return 0;
>  
> -	fd = open(mntpt, O_RDONLY);
> -	if (fd < 0)
> -		return -1;
> +	if (opts[OPT_LABEL]) {
> +		unsigned int maxlen = FSLABEL_MAX - 1;
>  
> -	/* Get fs label */
> -	if (!setlabel) {
> -		if (ioctl(fd, FS_IOC_GETFSLABEL, &label)) {
> -			close(fd);
> -			if (errno == ENOTTY)
> -				return -1;
> -			com_err(mntpt, errno, _("while trying to get fs label"));
> -			return 1;
> +		/* If it's extN file system, truncate the label
> +		   to appropriate size */
> +		if (mount_flags & EXT2_MF_EXTFS)
> +			maxlen = EXT2_LABEL_LEN;
> +		if (strlen(new_label) > maxlen) {
> +			fputs(_("Warning: label too long, truncating.\n"),
> +			      stderr);
> +			new_label[maxlen] = '\0';
>  		}
> -		close(fd);
> -		printf("%.*s\n", EXT2_LEN_STR(label));
> -		return 0;
> +		if (ioctl(fs_fd, FS_IOC_SETFSLABEL, new_label) == 0)
> +			opts[OPT_LABEL] = false;
>  	}
>  
> -	/* If it's extN file system, truncate the label to appropriate size */
> -	if (mnt_flags & EXT2_MF_EXTFS)
> -		maxlen = EXT2_LABEL_LEN;
> -	if (strlen(new_label) > maxlen) {
> -		fputs(_("Warning: label too long, truncating.\n"),
> -		      stderr);
> -		new_label[maxlen] = '\0';
> -	}
> +	if (ioctl(fs_fd, EXT4_IOC_GET_TUNE_SB_PARAM, &params))
> +		return 0;
>  
> -	/* Set fs label */
> -	if (ioctl(fd, FS_IOC_SETFSLABEL, new_label)) {
> -		close(fd);
> -		if (errno == ENOTTY)
> +	fs_set_ops = params.set_flags;
> +	fs_blocks_count = params.blocks_count;
> +	fs_feature_array[0] = params.feature_compat;
> +	fs_feature_array[1] = params.feature_incompat;
> +	fs_feature_array[2] = params.feature_ro_compat;
> +	kernel_set_mask[0] = params.set_feature_compat_mask;
> +	kernel_set_mask[1] = params.set_feature_incompat_mask;
> +	kernel_set_mask[2] = params.set_feature_ro_compat_mask;
> +	kernel_clear_mask[0] = params.clear_feature_compat_mask;
> +	kernel_clear_mask[1] = params.clear_feature_incompat_mask;
> +	kernel_clear_mask[2] = params.clear_feature_ro_compat_mask;
> +	default_mnt_opts = params.default_mnt_opts;
> +
> +	memset(&params, 0, sizeof(params));
> +
> +#define SIMPLE_SET_PARAM(OPT, FLAG, PARAM_FIELD, VALUE) \
> +	if (opts[OPT] && (fs_set_ops & FLAG)) { 	\
> +		params.set_flags |= FLAG; 		\
> +		params.PARAM_FIELD = VALUE;		\
> +	}
> +	SIMPLE_SET_PARAM(OPT_ERROR_BEHAVIOR, EXT4_TUNE_FL_ERRORS_BEHAVIOR,
> +			 errors_behavior, errors);
> +	SIMPLE_SET_PARAM(OPT_MOUNTCOUNT, EXT4_TUNE_FL_MNT_COUNT,
> +			 set_flags, mount_count);
> +	SIMPLE_SET_PARAM(OPT_MAX_MOUNTCOUNT, EXT4_TUNE_FL_MAX_MNT_COUNT,
> +			 set_flags, max_mount_count);
> +	SIMPLE_SET_PARAM(OPT_CHECKINTERVAL, EXT4_TUNE_FL_CHECKINTRVAL,
> +			 set_flags, interval);
> +	SIMPLE_SET_PARAM(OPT_CHECKTIME, EXT4_TUNE_FL_LAST_CHECK_TIME,
> +			 last_check_time, last_check_time);
> +	SIMPLE_SET_PARAM(OPT_RESUID, EXT4_TUNE_FL_RESERVED_UID,
> +			 reserved_uid, resuid);
> +	SIMPLE_SET_PARAM(OPT_RESGID, EXT4_TUNE_FL_RESERVED_GID,
> +			 reserved_gid, resgid);
> +	if (opts[OPT_RESERVED_RATIO] && !opts[OPT_RESERVED_BLOCKS]) {
> +		reserved_blocks = reserved_ratio * fs_blocks_count / 100.0;
> +		opts[OPT_RESERVED_BLOCKS] = true;
> +	}
> +	SIMPLE_SET_PARAM(OPT_RESERVED_BLOCKS, EXT4_TUNE_FL_RESERVED_BLOCKS,
> +			 reserved_blocks, reserved_blocks);
> +	SIMPLE_SET_PARAM(OPT_RAID_STRIDE, EXT4_TUNE_FL_RAID_STRIDE,
> +			 raid_stride, stride);
> +	SIMPLE_SET_PARAM(OPT_RAID_STRIPE_WIDTH, EXT4_TUNE_FL_RAID_STRIPE_WIDTH,
> +			 raid_stripe_width, stripe_width);
> +	SIMPLE_SET_PARAM(OPT_ENCODING, EXT4_TUNE_FL_ENCODING,
> +			encoding, encoding);
> +	SIMPLE_SET_PARAM(OPT_ENCODING_FLAGS, EXT4_TUNE_FL_ENCODING_FLAGS,
> +			encoding_flags, encoding_flags);
> +	if (opts[OPT_MNTOPTS] &&
> +	    (fs_set_ops & EXT4_TUNE_FL_DEFAULT_MNT_OPTS)) {
> +		if (e2p_edit_mntopts(mntopts_cmd, &default_mnt_opts, ~0)) {
> +			fprintf(stderr, _("Invalid mount option set: %s\n"),
> +				mntopts_cmd);
>  			return -1;
> -		com_err(mntpt, errno, _("while trying to set fs label"));
> -		return 1;
> +		}
> +		params.set_flags |= EXT4_TUNE_FL_DEFAULT_MNT_OPTS;
> +		params.default_mnt_opts = default_mnt_opts;
> +	}
> +	if (opts[OPT_MOUNT_OPTS] &&
> +	    (fs_set_ops & EXT4_TUNE_FL_MOUNT_OPTS)) {
> +		params.set_flags |= EXT4_TUNE_FL_MOUNT_OPTS;
> +		strncpy(params.mount_opts, ext_mount_opts,
> +			sizeof(params.mount_opts));
> +		params.mount_opts[sizeof(params.mount_opts) - 1] = 0;
> +	}
> +	if (opts[OPT_FEATURES] &&
> +	    (fs_set_ops & EXT4_TUNE_FL_FEATURES) &&
> +	    !e2p_edit_feature2(features_cmd, fs_feature_array,
> +			       kernel_set_mask, kernel_clear_mask,
> +			       NULL, NULL)) {
> +		params.set_flags |= EXT4_TUNE_FL_FEATURES;
> +		params.feature_compat = fs_feature_array[0];
> +		params.feature_incompat = fs_feature_array[1];
> +		params.feature_ro_compat = fs_feature_array[2];
> +	}
> +	if (opts[OPT_FORCE_FSCK] &&
> +	    (fs_set_ops & EXT4_TUNE_FL_FORCE_FSCK))
> +		params.set_flags |= EXT4_TUNE_FL_FORCE_FSCK;
> +
> +	if (ioctl(fs_fd, EXT4_IOC_SET_TUNE_SB_PARAM, &params) == 0) {
> +		if (opts[OPT_ERROR_BEHAVIOR])
> +			printf(_("Setting error behavior to %d\n"), errors);
> +		if (opts[OPT_MOUNTCOUNT])
> +			printf(_("Setting current mount count to %d\n"),
> +			       mount_count);
> +		if (opts[OPT_MAX_MOUNTCOUNT])
> +			printf(_("Setting maximal mount count to %d\n"),
> +			       max_mount_count);
> +		if (opts[OPT_CHECKINTERVAL])
> +			printf(_("Setting interval between checks to %lu seconds\n"),
> +			       interval);
> +		if (opts[OPT_CHECKTIME])
> +			printf(_("Setting time filesystem last checked to %s\n"),
> +			       ctime(&last_check_time));
> +		if (opts[OPT_RESUID])
> +			printf(_("Setting reserved blocks uid to %lu\n"),
> +			       resuid);
> +		if (opts[OPT_RESGID])
> +			printf(_("Setting reserved blocks gid to %lu\n"),
> +			       resgid);
> +		if (opts[OPT_RESERVED_BLOCKS])
> +			printf(_("Setting reserved blocks count to %llu\n"),
> +			       (unsigned long long) reserved_blocks);
> +		if (opts[OPT_RAID_STRIDE])
> +			printf(_("Setting stride size to %d\n"), stride);
> +		if (opts[OPT_RAID_STRIPE_WIDTH])
> +			printf(_("Setting stripe width to %d\n"),
> +			       stripe_width);
> +		if (opts[OPT_MOUNT_OPTS])
> +			printf(_("Setting extended default mount options to '%s'\n"),
> +			       ext_mount_opts);
> +		if (opts[OPT_ENCODING])
> +			printf(_("Setting encoding to '%s'\n"), encoding_str);
> +		if (opts[OPT_ENCODING_FLAGS])
> +			printf(_("Setting encoding_flags to '%s'\n"),
> +			       encoding_flags_str);
> +		if (opts[OPT_FORCE_FSCK])
> +			printf(_("Setting filesystem error flag to force fsck.\n"));
> +		opts[OPT_ERROR_BEHAVIOR] = opts[OPT_MOUNTCOUNT] =
> +			opts[OPT_MAX_MOUNTCOUNT] = opts[OPT_CHECKINTERVAL] =
> +			opts[OPT_CHECKTIME] = opts[OPT_RESUID] =
> +			opts[OPT_RESGID] = opts[OPT_RESERVED_RATIO] =
> +			opts[OPT_RESERVED_BLOCKS] = opts[OPT_MNTOPTS] =
> +			opts[OPT_RAID_STRIDE] = opts[OPT_RAID_STRIPE_WIDTH] =
> +			opts[OPT_MOUNT_OPTS] = opts[OPT_FEATURES] =
> +			opts[OPT_FORCE_FSCK] = opts[OPT_ENCODING] =
> +			opts[OPT_ENCODING_FLAGS] = false;
> +		printf("online tune superblock succeeded\n");
> +	} else {
> +		perror("ioctl EXT4_IOC_SET_TUNE_SB_PARAM");
> +		return -1;
>  	}
> -	close(fd);
> -	return 0;
> -#else
> -	return -1;

Shouldn't this still return 1 if this isn't being built on __linux__?

>  #endif
> +	return 0;
>  }
>  
>  #ifndef BUILD_AS_LIB
> @@ -3186,7 +3387,6 @@ int tune2fs_main(int argc, char **argv)
>  	io_manager io_ptr, io_ptr_orig = NULL;
>  	int rc = 0;
>  	char default_undo_file[1] = { 0 };
> -	char mntpt[PATH_MAX + 1] = { 0 };
>  	int fd = -1;
>  	struct fsuuid *fsuuid = NULL;
>  
> @@ -3220,19 +3420,21 @@ int tune2fs_main(int argc, char **argv)
>  #endif
>  		io_ptr = unix_io_manager;
>  
> -	/*
> -	 * Try the get/set fs label using ioctls before we even attempt
> -	 * to open the file system.
> -	 */
> -	if (opts[OPT_LABEL] || opts[OPT_PRINT_LABEL]) {
> -		rc = handle_fslabel(opts[OPT_LABEL]);
> -		if (rc != -1) {
> -#ifndef BUILD_AS_LIB
> -			exit(rc);
> +	if (get_mount_flags() < 0 || try_mounted_tune2fs() << 0) {

Why shift left here                                        ^^ ??

> +#ifdef BUILD_AS_LIB
> +		return -1;
> +#else
> +		exit(1);
> +#endif
> +	}
> +
> +	if (!tune_opts_requested()) {
> +		/* printf("No further tune opts left\n"); */
> +#ifdef BUILD_AS_LIB
> +		return 0;
> +#else
> +		exit(0);
>  #endif
> -			return rc;
> -		}
> -		rc = 0;
>  	}
>  
>  retry_open:
> @@ -3338,16 +3540,6 @@ retry_open:
>  		goto closefs;
>  	}
>  
> -	retval = ext2fs_check_mount_point(device_name, &mount_flags,
> -					mntpt, sizeof(mntpt));
> -	if (retval) {
> -		com_err("ext2fs_check_mount_point", retval,
> -			_("while determining whether %s is mounted."),
> -			device_name);
> -		rc = 1;
> -		goto closefs;
> -	}
> -
>  #ifdef NO_RECOVERY
>  	/* Warn if file system needs recovery and it is opened for writing. */
>  	if ((open_flag & EXT2_FLAG_RW) && !(mount_flags & EXT2_MF_MOUNTED) &&
> @@ -3382,9 +3574,6 @@ _("Warning: The journal is dirty. You may wish to replay the journal like:\n\n"
>  	fs->flags |= EXT2_FLAG_SUPER_ONLY;
>  
>  	if (opts[OPT_MAX_MOUNTCOUNT]) {
> -		if (max_mount_count == 65536)
> -			max_mount_count = EXT2_DFL_MAX_MNT_COUNT +
> -				(random() % EXT2_DFL_MAX_MNT_COUNT);
>  		sb->s_max_mnt_count = max_mount_count;
>  		ext2fs_mark_super_dirty(fs);
>  		printf(_("Setting maximal mount count to %d\n"),
> @@ -3410,13 +3599,6 @@ _("Warning: The journal is dirty. You may wish to replay the journal like:\n\n"
>  		}
>  	}
>  	if (opts[OPT_CHECKINTERVAL]) {
> -		if ((unsigned long long)interval >= (1ULL << 32)) {
> -			com_err(program_name, 0,
> -				_("interval between checks is too big (%lu)"),
> -				interval);
> -			rc = 1;
> -			goto closefs;
> -		}
>  		sb->s_checkinterval = interval;
>  		ext2fs_mark_super_dirty(fs);
>  		printf(_("Setting interval between checks to %lu seconds\n"),
> @@ -3494,7 +3676,7 @@ _("Warning: The journal is dirty. You may wish to replay the journal like:\n\n"
>  			sizeof(sb->s_last_mounted));
>  		ext2fs_mark_super_dirty(fs);
>  	}
> -	if (mntopts_cmd) {
> +	if (opts[OPT_MNTOPTS]) {
>  		rc = update_mntopts(fs, mntopts_cmd);
>  		if (rc)
>  			goto closefs;
> -- 
> 2.51.0
> 
> 

  reply	other threads:[~2025-09-18 17:47 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-09-17  3:28 [PATCH 0/3] E2fsprogs: tune2fs: use an ioctl to update mounted fs Theodore Ts'o
2025-09-17  3:28 ` [PATCH 1/3] tune2fs: reorganize command-line flag handling Theodore Ts'o
2025-09-17  3:28 ` [PATCH 2/3] tune2fs: rework parse_extended_opts() so it only parses the option string Theodore Ts'o
2025-09-17  6:15   ` Andreas Dilger
2025-09-17  3:28 ` [PATCH 3/3] tune2fs: try to use the SET_TUNE_SB_PARAM ioctl on mounted file systems Theodore Ts'o
2025-09-18 17:47   ` Darrick J. Wong [this message]
2025-09-19 15:01     ` Theodore Ts'o
2025-09-17  6:26 ` [PATCH 0/3] E2fsprogs: tune2fs: use an ioctl to update mounted fs Andreas Dilger
2025-09-18 16:55   ` Theodore Ts'o

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=20250918174724.GH8084@frogsfrogsfrogs \
    --to=djwong@kernel.org \
    --cc=linux-ext4@vger.kernel.org \
    --cc=tytso@mit.edu \
    /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
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.