From: Keith Packard <keithp@keithp.com> To: linux-kernel@vger.kernel.org, Dave Airlie <airlied@redhat.com>, Daniel Vetter <daniel@ffwll.ch> Cc: Keith Packard <keithp@keithp.com>, dri-devel@lists.freedesktop.org Subject: [PATCH 4/5] drm: Check mode object lease status in all master ioctl paths [v2] Date: Fri, 28 Apr 2017 23:07:01 -0700 [thread overview] Message-ID: <20170429060702.6656-5-keithp@keithp.com> (raw) In-Reply-To: <20170429060702.6656-1-keithp@keithp.com> Attempts to modify un-leased objects are rejected with an error. Information returned about unleased objects is modified to make them appear unusable and/or disconnected. Changes for v2 as suggested by Daniel Vetter <daniel.vetter@ffwll.ch>: With the change in the __drm_mode_object_find API to pass the file_priv along, we can now centralize most of the lease-based access checks in that function. A few places skip that API and require in-line checks. Signed-off-by: Keith Packard <keithp@keithp.com> --- drivers/gpu/drm/drm_auth.c | 2 +- drivers/gpu/drm/drm_connector.c | 8 +++--- drivers/gpu/drm/drm_encoder.c | 8 +++--- drivers/gpu/drm/drm_mode_config.c | 52 ++++++++++++++++++++++++--------------- drivers/gpu/drm/drm_mode_object.c | 22 +++++++++++++++++ drivers/gpu/drm/drm_plane.c | 6 +++-- 6 files changed, 69 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c index 1db4f63860d1..44c99d12f4c1 100644 --- a/drivers/gpu/drm/drm_auth.c +++ b/drivers/gpu/drm/drm_auth.c @@ -303,7 +303,7 @@ void drm_master_release(struct drm_file *file_priv) */ bool drm_is_current_master(struct drm_file *fpriv) { - return fpriv->is_master && fpriv->master == fpriv->minor->dev->master; + return fpriv->is_master && drm_lease_owner(fpriv->master) == fpriv->minor->dev->master; } EXPORT_SYMBOL(drm_is_current_master); diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 670c20d5660c..dbf34f08363b 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -1094,7 +1094,8 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, } for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) - if (connector->encoder_ids[i] != 0) + if (connector->encoder_ids[i] != 0 && + drm_lease_held(file_priv, connector->encoder_ids[i])) encoders_count++; if (out_resp->count_modes == 0) { @@ -1118,7 +1119,7 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); encoder = drm_connector_get_encoder(connector); - if (encoder) + if (encoder && drm_lease_held(file_priv, encoder->base.id)) out_resp->encoder_id = encoder->base.id; else out_resp->encoder_id = 0; @@ -1156,7 +1157,8 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, copied = 0; encoder_ptr = (uint32_t __user *)(unsigned long)(out_resp->encoders_ptr); for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { - if (connector->encoder_ids[i] != 0) { + if (connector->encoder_ids[i] != 0 && + drm_lease_held(file_priv, connector->encoder_ids[i])) { if (put_user(connector->encoder_ids[i], encoder_ptr + copied)) { ret = -EFAULT; diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c index dbaedd4e12e6..512d0b6cb7d2 100644 --- a/drivers/gpu/drm/drm_encoder.c +++ b/drivers/gpu/drm/drm_encoder.c @@ -211,7 +211,7 @@ int drm_mode_getencoder(struct drm_device *dev, void *data, drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); crtc = drm_encoder_get_crtc(encoder); - if (crtc) + if (crtc && drm_lease_held(file_priv, crtc->base.id)) enc_resp->crtc_id = crtc->base.id; else enc_resp->crtc_id = 0; @@ -219,8 +219,10 @@ int drm_mode_getencoder(struct drm_device *dev, void *data, enc_resp->encoder_type = encoder->encoder_type; enc_resp->encoder_id = encoder->base.id; - enc_resp->possible_crtcs = encoder->possible_crtcs; - enc_resp->possible_clones = encoder->possible_clones; + enc_resp->possible_crtcs = drm_lease_filter_crtcs(file_priv, + encoder->possible_crtcs); + enc_resp->possible_clones = drm_lease_filter_encoders(file_priv, + encoder->possible_clones); return 0; } diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 2735a5847ffa..bb6b64e594b7 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -131,14 +131,20 @@ int drm_mode_getresources(struct drm_device *dev, void *data, /* mode_config.mutex protects the connector list against e.g. DP MST * connector hot-adding. CRTC/Plane lists are invariant. */ mutex_lock(&dev->mode_config.mutex); - drm_for_each_crtc(crtc, dev) - crtc_count++; + drm_for_each_crtc(crtc, dev) { + if (drm_lease_held(file_priv, crtc->base.id)) + crtc_count++; + } - drm_for_each_connector(connector, dev) - connector_count++; + drm_for_each_connector(connector, dev) { + if (drm_lease_held(file_priv, connector->base.id)) + connector_count++; + } - drm_for_each_encoder(encoder, dev) - encoder_count++; + drm_for_each_encoder(encoder, dev) { + if (drm_lease_held(file_priv, encoder->base.id)) + encoder_count++; + } card_res->max_height = dev->mode_config.max_height; card_res->min_height = dev->mode_config.min_height; @@ -150,11 +156,13 @@ int drm_mode_getresources(struct drm_device *dev, void *data, copied = 0; crtc_id = (uint32_t __user *)(unsigned long)card_res->crtc_id_ptr; drm_for_each_crtc(crtc, dev) { - if (put_user(crtc->base.id, crtc_id + copied)) { - ret = -EFAULT; - goto out; + if (drm_lease_held(file_priv, crtc->base.id)) { + if (put_user(crtc->base.id, crtc_id + copied)) { + ret = -EFAULT; + goto out; + } + copied++; } - copied++; } } card_res->count_crtcs = crtc_count; @@ -164,12 +172,14 @@ int drm_mode_getresources(struct drm_device *dev, void *data, copied = 0; encoder_id = (uint32_t __user *)(unsigned long)card_res->encoder_id_ptr; drm_for_each_encoder(encoder, dev) { - if (put_user(encoder->base.id, encoder_id + - copied)) { - ret = -EFAULT; - goto out; + if (drm_lease_held(file_priv, encoder->base.id)) { + if (put_user(encoder->base.id, encoder_id + + copied)) { + ret = -EFAULT; + goto out; + } + copied++; } - copied++; } } card_res->count_encoders = encoder_count; @@ -179,12 +189,14 @@ int drm_mode_getresources(struct drm_device *dev, void *data, copied = 0; connector_id = (uint32_t __user *)(unsigned long)card_res->connector_id_ptr; drm_for_each_connector(connector, dev) { - if (put_user(connector->base.id, - connector_id + copied)) { - ret = -EFAULT; - goto out; + if (drm_lease_held(file_priv, connector->base.id)) { + if (put_user(connector->base.id, + connector_id + copied)) { + ret = -EFAULT; + goto out; + } + copied++; } - copied++; } } card_res->count_connectors = connector_count; diff --git a/drivers/gpu/drm/drm_mode_object.c b/drivers/gpu/drm/drm_mode_object.c index daddc7640139..9763787492cc 100644 --- a/drivers/gpu/drm/drm_mode_object.c +++ b/drivers/gpu/drm/drm_mode_object.c @@ -107,6 +107,25 @@ void drm_mode_object_unregister(struct drm_device *dev, mutex_unlock(&dev->mode_config.idr_mutex); } +/** + * drm_lease_required - check types which must be leased to be used + * @type: type of object + * + * Returns whether the provided type of drm_mode_object must + * be owned or leased to be used by a process. + */ +static bool drm_lease_required(uint32_t type) +{ + switch(type) { + case DRM_MODE_OBJECT_CRTC: + case DRM_MODE_OBJECT_CONNECTOR: + case DRM_MODE_OBJECT_ENCODER: + return true; + default: + return false; + } +} + struct drm_mode_object *__drm_mode_object_find(struct drm_device *dev, struct drm_file *file_priv, uint32_t id, uint32_t type) @@ -120,6 +139,9 @@ struct drm_mode_object *__drm_mode_object_find(struct drm_device *dev, if (obj && obj->id != id) obj = NULL; + if (obj && drm_lease_required(obj->type) && !_drm_lease_held(file_priv, obj->id)) + obj = NULL; + if (obj && obj->free_cb) { if (!kref_get_unless_zero(&obj->refcount)) obj = NULL; diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c index 819f74616f34..c9c687cdc747 100644 --- a/drivers/gpu/drm/drm_plane.c +++ b/drivers/gpu/drm/drm_plane.c @@ -392,7 +392,7 @@ int drm_mode_getplane(struct drm_device *dev, void *data, return -ENOENT; drm_modeset_lock(&plane->mutex, NULL); - if (plane->crtc) + if (plane->crtc && drm_lease_held(file_priv, plane->crtc->base.id)) plane_resp->crtc_id = plane->crtc->base.id; else plane_resp->crtc_id = 0; @@ -404,7 +404,9 @@ int drm_mode_getplane(struct drm_device *dev, void *data, drm_modeset_unlock(&plane->mutex); plane_resp->plane_id = plane->base.id; - plane_resp->possible_crtcs = plane->possible_crtcs; + plane_resp->possible_crtcs = drm_lease_filter_crtcs(file_priv, + plane->possible_crtcs); + plane_resp->gamma_size = 0; /* -- 2.11.0
WARNING: multiple messages have this Message-ID (diff)
From: Keith Packard <keithp@keithp.com> To: linux-kernel@vger.kernel.org, Dave Airlie <airlied@redhat.com>, Daniel Vetter <daniel@ffwll.ch> Cc: Keith Packard <keithp@keithp.com>, dri-devel@lists.freedesktop.org Subject: [PATCH 4/5] drm: Check mode object lease status in all master ioctl paths [v2] Date: Fri, 28 Apr 2017 23:07:01 -0700 [thread overview] Message-ID: <20170429060702.6656-5-keithp@keithp.com> (raw) In-Reply-To: <20170429060702.6656-1-keithp@keithp.com> Attempts to modify un-leased objects are rejected with an error. Information returned about unleased objects is modified to make them appear unusable and/or disconnected. Changes for v2 as suggested by Daniel Vetter <daniel.vetter@ffwll.ch>: With the change in the __drm_mode_object_find API to pass the file_priv along, we can now centralize most of the lease-based access checks in that function. A few places skip that API and require in-line checks. Signed-off-by: Keith Packard <keithp@keithp.com> --- drivers/gpu/drm/drm_auth.c | 2 +- drivers/gpu/drm/drm_connector.c | 8 +++--- drivers/gpu/drm/drm_encoder.c | 8 +++--- drivers/gpu/drm/drm_mode_config.c | 52 ++++++++++++++++++++++++--------------- drivers/gpu/drm/drm_mode_object.c | 22 +++++++++++++++++ drivers/gpu/drm/drm_plane.c | 6 +++-- 6 files changed, 69 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/drm_auth.c b/drivers/gpu/drm/drm_auth.c index 1db4f63860d1..44c99d12f4c1 100644 --- a/drivers/gpu/drm/drm_auth.c +++ b/drivers/gpu/drm/drm_auth.c @@ -303,7 +303,7 @@ void drm_master_release(struct drm_file *file_priv) */ bool drm_is_current_master(struct drm_file *fpriv) { - return fpriv->is_master && fpriv->master == fpriv->minor->dev->master; + return fpriv->is_master && drm_lease_owner(fpriv->master) == fpriv->minor->dev->master; } EXPORT_SYMBOL(drm_is_current_master); diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 670c20d5660c..dbf34f08363b 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -1094,7 +1094,8 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, } for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) - if (connector->encoder_ids[i] != 0) + if (connector->encoder_ids[i] != 0 && + drm_lease_held(file_priv, connector->encoder_ids[i])) encoders_count++; if (out_resp->count_modes == 0) { @@ -1118,7 +1119,7 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); encoder = drm_connector_get_encoder(connector); - if (encoder) + if (encoder && drm_lease_held(file_priv, encoder->base.id)) out_resp->encoder_id = encoder->base.id; else out_resp->encoder_id = 0; @@ -1156,7 +1157,8 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, copied = 0; encoder_ptr = (uint32_t __user *)(unsigned long)(out_resp->encoders_ptr); for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { - if (connector->encoder_ids[i] != 0) { + if (connector->encoder_ids[i] != 0 && + drm_lease_held(file_priv, connector->encoder_ids[i])) { if (put_user(connector->encoder_ids[i], encoder_ptr + copied)) { ret = -EFAULT; diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c index dbaedd4e12e6..512d0b6cb7d2 100644 --- a/drivers/gpu/drm/drm_encoder.c +++ b/drivers/gpu/drm/drm_encoder.c @@ -211,7 +211,7 @@ int drm_mode_getencoder(struct drm_device *dev, void *data, drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); crtc = drm_encoder_get_crtc(encoder); - if (crtc) + if (crtc && drm_lease_held(file_priv, crtc->base.id)) enc_resp->crtc_id = crtc->base.id; else enc_resp->crtc_id = 0; @@ -219,8 +219,10 @@ int drm_mode_getencoder(struct drm_device *dev, void *data, enc_resp->encoder_type = encoder->encoder_type; enc_resp->encoder_id = encoder->base.id; - enc_resp->possible_crtcs = encoder->possible_crtcs; - enc_resp->possible_clones = encoder->possible_clones; + enc_resp->possible_crtcs = drm_lease_filter_crtcs(file_priv, + encoder->possible_crtcs); + enc_resp->possible_clones = drm_lease_filter_encoders(file_priv, + encoder->possible_clones); return 0; } diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 2735a5847ffa..bb6b64e594b7 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -131,14 +131,20 @@ int drm_mode_getresources(struct drm_device *dev, void *data, /* mode_config.mutex protects the connector list against e.g. DP MST * connector hot-adding. CRTC/Plane lists are invariant. */ mutex_lock(&dev->mode_config.mutex); - drm_for_each_crtc(crtc, dev) - crtc_count++; + drm_for_each_crtc(crtc, dev) { + if (drm_lease_held(file_priv, crtc->base.id)) + crtc_count++; + } - drm_for_each_connector(connector, dev) - connector_count++; + drm_for_each_connector(connector, dev) { + if (drm_lease_held(file_priv, connector->base.id)) + connector_count++; + } - drm_for_each_encoder(encoder, dev) - encoder_count++; + drm_for_each_encoder(encoder, dev) { + if (drm_lease_held(file_priv, encoder->base.id)) + encoder_count++; + } card_res->max_height = dev->mode_config.max_height; card_res->min_height = dev->mode_config.min_height; @@ -150,11 +156,13 @@ int drm_mode_getresources(struct drm_device *dev, void *data, copied = 0; crtc_id = (uint32_t __user *)(unsigned long)card_res->crtc_id_ptr; drm_for_each_crtc(crtc, dev) { - if (put_user(crtc->base.id, crtc_id + copied)) { - ret = -EFAULT; - goto out; + if (drm_lease_held(file_priv, crtc->base.id)) { + if (put_user(crtc->base.id, crtc_id + copied)) { + ret = -EFAULT; + goto out; + } + copied++; } - copied++; } } card_res->count_crtcs = crtc_count; @@ -164,12 +172,14 @@ int drm_mode_getresources(struct drm_device *dev, void *data, copied = 0; encoder_id = (uint32_t __user *)(unsigned long)card_res->encoder_id_ptr; drm_for_each_encoder(encoder, dev) { - if (put_user(encoder->base.id, encoder_id + - copied)) { - ret = -EFAULT; - goto out; + if (drm_lease_held(file_priv, encoder->base.id)) { + if (put_user(encoder->base.id, encoder_id + + copied)) { + ret = -EFAULT; + goto out; + } + copied++; } - copied++; } } card_res->count_encoders = encoder_count; @@ -179,12 +189,14 @@ int drm_mode_getresources(struct drm_device *dev, void *data, copied = 0; connector_id = (uint32_t __user *)(unsigned long)card_res->connector_id_ptr; drm_for_each_connector(connector, dev) { - if (put_user(connector->base.id, - connector_id + copied)) { - ret = -EFAULT; - goto out; + if (drm_lease_held(file_priv, connector->base.id)) { + if (put_user(connector->base.id, + connector_id + copied)) { + ret = -EFAULT; + goto out; + } + copied++; } - copied++; } } card_res->count_connectors = connector_count; diff --git a/drivers/gpu/drm/drm_mode_object.c b/drivers/gpu/drm/drm_mode_object.c index daddc7640139..9763787492cc 100644 --- a/drivers/gpu/drm/drm_mode_object.c +++ b/drivers/gpu/drm/drm_mode_object.c @@ -107,6 +107,25 @@ void drm_mode_object_unregister(struct drm_device *dev, mutex_unlock(&dev->mode_config.idr_mutex); } +/** + * drm_lease_required - check types which must be leased to be used + * @type: type of object + * + * Returns whether the provided type of drm_mode_object must + * be owned or leased to be used by a process. + */ +static bool drm_lease_required(uint32_t type) +{ + switch(type) { + case DRM_MODE_OBJECT_CRTC: + case DRM_MODE_OBJECT_CONNECTOR: + case DRM_MODE_OBJECT_ENCODER: + return true; + default: + return false; + } +} + struct drm_mode_object *__drm_mode_object_find(struct drm_device *dev, struct drm_file *file_priv, uint32_t id, uint32_t type) @@ -120,6 +139,9 @@ struct drm_mode_object *__drm_mode_object_find(struct drm_device *dev, if (obj && obj->id != id) obj = NULL; + if (obj && drm_lease_required(obj->type) && !_drm_lease_held(file_priv, obj->id)) + obj = NULL; + if (obj && obj->free_cb) { if (!kref_get_unless_zero(&obj->refcount)) obj = NULL; diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c index 819f74616f34..c9c687cdc747 100644 --- a/drivers/gpu/drm/drm_plane.c +++ b/drivers/gpu/drm/drm_plane.c @@ -392,7 +392,7 @@ int drm_mode_getplane(struct drm_device *dev, void *data, return -ENOENT; drm_modeset_lock(&plane->mutex, NULL); - if (plane->crtc) + if (plane->crtc && drm_lease_held(file_priv, plane->crtc->base.id)) plane_resp->crtc_id = plane->crtc->base.id; else plane_resp->crtc_id = 0; @@ -404,7 +404,9 @@ int drm_mode_getplane(struct drm_device *dev, void *data, drm_modeset_unlock(&plane->mutex); plane_resp->plane_id = plane->base.id; - plane_resp->possible_crtcs = plane->possible_crtcs; + plane_resp->possible_crtcs = drm_lease_filter_crtcs(file_priv, + plane->possible_crtcs); + plane_resp->gamma_size = 0; /* -- 2.11.0 _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
next prev parent reply other threads:[~2017-04-29 6:07 UTC|newest] Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top 2017-04-01 17:08 [PATCH 0/4] drm: Add mode resource leasing Keith Packard 2017-04-01 17:08 ` [PATCH 1/4] drm: Add new LEASE debug level Keith Packard 2017-04-01 17:08 ` [PATCH 2/4] drm: Add drm_object lease infrastructure Keith Packard 2017-04-02 13:38 ` Daniel Vetter 2017-04-02 13:38 ` Daniel Vetter 2017-04-02 16:31 ` Keith Packard 2017-04-10 5:16 ` Michel Dänzer 2017-04-10 5:16 ` Michel Dänzer 2017-04-02 17:51 ` Daniel Vetter 2017-04-02 17:51 ` Daniel Vetter 2017-04-02 19:59 ` Keith Packard 2017-04-01 17:08 ` [PATCH 3/4] drm: Check mode object lease status in all master ioctl paths Keith Packard 2017-04-02 13:19 ` Daniel Vetter 2017-04-02 13:19 ` Daniel Vetter 2017-04-02 16:37 ` Keith Packard 2017-04-03 7:49 ` Daniel Vetter 2017-04-03 7:49 ` Daniel Vetter 2017-04-10 1:06 ` Keith Packard 2017-04-10 1:06 ` Keith Packard 2017-04-01 17:08 ` [PATCH 4/4] drm: Add four ioctls for managing drm mode object leases Keith Packard 2017-04-02 13:23 ` Daniel Vetter 2017-04-02 13:23 ` Daniel Vetter 2017-04-02 16:44 ` Keith Packard 2017-04-29 6:06 ` [PATCH 0/5] drm: Add mode resource leasing [v2] Keith Packard 2017-04-29 6:06 ` Keith Packard 2017-04-29 6:06 ` [PATCH 1/5] drm: Pass struct drm_file * to __drm_mode_object_find Keith Packard 2017-04-29 6:06 ` Keith Packard 2017-04-29 6:06 ` [PATCH 2/5] drm: Add new LEASE debug level Keith Packard 2017-04-29 6:06 ` Keith Packard 2017-04-29 6:07 ` [PATCH 3/5] drm: Add drm_object lease infrastructure [v2] Keith Packard 2017-04-29 6:07 ` Keith Packard 2017-04-29 6:07 ` Keith Packard [this message] 2017-04-29 6:07 ` [PATCH 4/5] drm: Check mode object lease status in all master ioctl paths [v2] Keith Packard 2017-04-29 6:07 ` [PATCH 5/5] drm: Add three ioctls for managing drm mode object leases [v2] Keith Packard 2017-04-29 6:07 ` Keith Packard
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=20170429060702.6656-5-keithp@keithp.com \ --to=keithp@keithp.com \ --cc=airlied@redhat.com \ --cc=daniel@ffwll.ch \ --cc=dri-devel@lists.freedesktop.org \ --cc=linux-kernel@vger.kernel.org \ /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: linkBe 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.