From: Jean Delvare <khali-PUYAD+kWke1g9hUCZPvPmw@public.gmane.org>
To: David Brownell <david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
Cc: i2c-GZX6beZjE8VD60Wz+7aTrA@public.gmane.org
Subject: Re: i2c-remove-redundant-i2c_client-list.patch
Date: Sun, 6 Jan 2008 12:23:56 +0100 [thread overview]
Message-ID: <20080106122356.78556b8a@hyperion.delvare> (raw)
In-Reply-To: <200712291905.15160.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
Hi David,
On Sat, 29 Dec 2007 19:05:14 -0800, David Brownell wrote:
> Remove further duplication between i2c core and driver model: the
> per-adapter list of clients (adapter->clients, client->list) and
> its lock (adapter->clist_lock) duplicate adapter->dev.children.
>
> LIGHTLY TESTED ... goes on top of two patches from Jean's I2C queue,
>
> i2c-remove-redundant-i2c_adapter-list.patch
> i2c-remove-redundant-i2c_driver-list.patch
>
> Continuing in that naming scheme, this might be called
>
> i2c-remove-redundant-i2c_client-list.patch
>
Review:
> ---
> drivers/i2c/i2c-core.c | 189 ++++++++++++++++++++++---------------------------
> drivers/i2c/i2c-dev.c | 33 ++++----
> include/linux/i2c.h | 4 -
> 3 files changed, 102 insertions(+), 124 deletions(-)
>
> --- a/drivers/i2c/i2c-core.c
> +++ b/drivers/i2c/i2c-core.c
> @@ -256,7 +256,6 @@ EXPORT_SYMBOL_GPL(i2c_new_device);
> */
> void i2c_unregister_device(struct i2c_client *client)
> {
> - struct i2c_adapter *adapter = client->adapter;
> struct i2c_driver *driver = client->driver;
>
> if (driver && !is_newstyle_driver(driver)) {
> @@ -265,11 +264,6 @@ void i2c_unregister_device(struct i2c_cl
> WARN_ON(1);
> return;
> }
> -
> - mutex_lock(&adapter->clist_lock);
> - list_del(&client->list);
> - mutex_unlock(&adapter->clist_lock);
> -
> device_unregister(&client->dev);
> }
> EXPORT_SYMBOL_GPL(i2c_unregister_device);
> @@ -381,8 +375,6 @@ static int i2c_register_adapter(struct i
> int res = 0, dummy;
>
> mutex_init(&adap->bus_lock);
> - mutex_init(&adap->clist_lock);
> - INIT_LIST_HEAD(&adap->clients);
>
> mutex_lock(&core_lists);
>
> @@ -525,6 +517,38 @@ static int i2c_do_del_adapter(struct dev
> return res;
> }
>
> +static struct i2c_client *verify_client(struct device *dev)
> +{
> + if (dev->bus != &i2c_bus_type)
Is this paranoia, or can this test really succeed? I thought that all
children of an i2c adapter node would always be i2c clients.
> + return NULL;
> + return to_i2c_client(dev);
> +}
> +
> +static int detach_all_clients(struct device *dev, void *x)
> +{
> + struct i2c_client *client = verify_client(dev);
> + struct i2c_driver *driver;
> + int res;
> +
> + if (!client)
> + return 0;
> +
> + driver = client->driver;
> +
> + /* new style, follow standard driver model */
> + if (!driver || is_newstyle_driver(driver)) {
> + i2c_unregister_device(client);
> + return 0;
> + }
> +
> + /* legacy drivers create and remove clients themselves */
> + if ((res = driver->detach_client(client)))
> + dev_err(dev, "detach_client [%s] failed at address 0x%02x\n",
> + client->name, client->addr);
> +
> + return res;
> +}
> +
> /**
> * i2c_del_adapter - unregister I2C adapter
> * @adap: the adapter being unregistered
> @@ -535,8 +559,6 @@ static int i2c_do_del_adapter(struct dev
> */
> int i2c_del_adapter(struct i2c_adapter *adap)
> {
> - struct list_head *item, *_n;
> - struct i2c_client *client;
> int res = 0;
>
> mutex_lock(&core_lists);
> @@ -557,26 +579,9 @@ int i2c_del_adapter(struct i2c_adapter *
>
> /* detach any active clients. This must be done first, because
> * it can fail; in which case we give up. */
> - list_for_each_safe(item, _n, &adap->clients) {
> - struct i2c_driver *driver;
> -
> - client = list_entry(item, struct i2c_client, list);
> - driver = client->driver;
> -
> - /* new style, follow standard driver model */
> - if (!driver || is_newstyle_driver(driver)) {
> - i2c_unregister_device(client);
> - continue;
> - }
> -
> - /* legacy drivers create and remove clients themselves */
> - if ((res = driver->detach_client(client))) {
> - dev_err(&adap->dev, "detach_client failed for client "
> - "[%s] at address 0x%02x\n", client->name,
> - client->addr);
> - goto out_unlock;
> - }
> - }
> + res = device_for_each_child(&adap->dev, NULL, detach_all_clients);
> + if (res)
> + goto out_unlock;
>
> /* clean up the sysfs representation */
> init_completion(&adap->dev_released);
> @@ -655,6 +660,20 @@ int i2c_register_driver(struct module *o
> }
> EXPORT_SYMBOL(i2c_register_driver);
>
> +static int detach_legacy_clients(struct device *dev, void *driver)
> +{
> + struct i2c_client *client = verify_client(dev);
> +
> + if (client && client->driver == driver) {
> + dev_dbg(dev, "detaching client [%s] at 0x%02x\n",
> + client->name, client->addr);
> + if (client->driver->detach_client(client))
> + dev_err(dev, "failed detach_client [%s] at 0x%02x\n",
> + client->name, client->addr);
> + }
> + return 0;
> +}
> +
> /**
> * i2c_del_driver - unregister I2C driver
> * @driver: the driver being unregistered
> @@ -662,8 +681,6 @@ EXPORT_SYMBOL(i2c_register_driver);
> */
> void i2c_del_driver(struct i2c_driver *driver)
> {
> - struct list_head *item2, *_n;
> - struct i2c_client *client;
> struct i2c_adapter *adap;
>
> mutex_lock(&core_lists);
> @@ -684,22 +701,9 @@ void i2c_del_driver(struct i2c_driver *d
> "for driver [%s]\n",
> driver->driver.name);
> }
> - } else {
> - list_for_each_safe(item2, _n, &adap->clients) {
> - client = list_entry(item2, struct i2c_client, list);
> - if (client->driver != driver)
> - continue;
> - dev_dbg(&adap->dev, "detaching client [%s] "
> - "at 0x%02x\n", client->name,
> - client->addr);
> - if (driver->detach_client(client)) {
> - dev_err(&adap->dev, "detach_client "
> - "failed for client [%s] at "
> - "0x%02x\n", client->name,
> - client->addr);
> - }
> - }
> - }
> + } else
> + device_for_each_child(&adap->dev, driver,
> + detach_legacy_clients);
> }
> up(&i2c_adapter_class.sem);
>
> @@ -713,28 +717,19 @@ EXPORT_SYMBOL(i2c_del_driver);
>
> /* ------------------------------------------------------------------------- */
>
> -static int __i2c_check_addr(struct i2c_adapter *adapter, unsigned int addr)
> +static int i2c_checkaddr(struct device *dev, void *addrp)
I don't much like this name, why don't you keep __i2c_check_addr?
> {
> - struct list_head *item;
> - struct i2c_client *client;
> + struct i2c_client *client = verify_client(dev);
> + int addr = *(int *)addrp;
>
> - list_for_each(item,&adapter->clients) {
> - client = list_entry(item, struct i2c_client, list);
> - if (client->addr == addr)
> - return -EBUSY;
> - }
> + if (client && client->addr == addr)
> + return -EBUSY;
> return 0;
> }
>
> static int i2c_check_addr(struct i2c_adapter *adapter, int addr)
> {
> - int rval;
> -
> - mutex_lock(&adapter->clist_lock);
> - rval = __i2c_check_addr(adapter, addr);
> - mutex_unlock(&adapter->clist_lock);
> -
> - return rval;
> + return device_for_each_child(&adapter->dev, &addr, i2c_checkaddr);
> }
>
> int i2c_attach_client(struct i2c_client *client)
> @@ -742,13 +737,6 @@ int i2c_attach_client(struct i2c_client
> struct i2c_adapter *adapter = client->adapter;
> int res = 0;
>
> - mutex_lock(&adapter->clist_lock);
> - if (__i2c_check_addr(client->adapter, client->addr)) {
> - res = -EBUSY;
> - goto out_unlock;
> - }
> - list_add_tail(&client->list,&adapter->clients);
> -
> client->usage_count = 0;
>
> client->dev.parent = &client->adapter->dev;
> @@ -765,12 +753,16 @@ int i2c_attach_client(struct i2c_client
>
> snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),
> "%d-%04x", i2c_adapter_id(adapter), client->addr);
> + res = device_register(&client->dev);
> + if (res) {
> + dev_err(&adapter->dev,
> + "Failed to register i2c client %s at 0x%02x (%d)\n",
> + client->name, client->addr, res);
> + return res;
> + }
> +
> dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n",
> client->name, client->dev.bus_id);
> - res = device_register(&client->dev);
> - if (res)
> - goto out_list;
> - mutex_unlock(&adapter->clist_lock);
>
> if (adapter->client_register) {
> if (adapter->client_register(client)) {
> @@ -781,14 +773,6 @@ int i2c_attach_client(struct i2c_client
> }
>
> return 0;
> -
> -out_list:
> - list_del(&client->list);
> - dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x "
> - "(%d)\n", client->name, client->addr, res);
> -out_unlock:
> - mutex_unlock(&adapter->clist_lock);
> - return res;
> }
> EXPORT_SYMBOL(i2c_attach_client);
>
> @@ -813,11 +797,8 @@ int i2c_detach_client(struct i2c_client
> }
> }
>
> - mutex_lock(&adapter->clist_lock);
> - list_del(&client->list);
> init_completion(&client->released);
> device_unregister(&client->dev);
> - mutex_unlock(&adapter->clist_lock);
> wait_for_completion(&client->released);
>
> out:
> @@ -873,24 +854,28 @@ int i2c_release_client(struct i2c_client
> }
> EXPORT_SYMBOL(i2c_release_client);
>
> +struct i2c_cmd_arg {
> + unsigned cmd;
> + void *arg;
> +};
> +
> +static int i2c_cmd(struct device *dev, void *_arg)
> +{
> + struct i2c_client *client = verify_client(dev);
> + struct i2c_cmd_arg *arg = _arg;
> +
> + if (client)
> + client->driver->command(client, arg->cmd, arg->arg);
client->driver->command may or may not be defined. The original code
was checking for this, and so should yours.
> + return 0;
> +}
> +
> void i2c_clients_command(struct i2c_adapter *adap, unsigned int cmd, void *arg)
> {
> - struct list_head *item;
> - struct i2c_client *client;
> + struct i2c_cmd_arg cmd_arg;
>
> - mutex_lock(&adap->clist_lock);
> - list_for_each(item,&adap->clients) {
> - client = list_entry(item, struct i2c_client, list);
> - if (!try_module_get(client->driver->driver.owner))
> - continue;
> - if (NULL != client->driver->command) {
> - mutex_unlock(&adap->clist_lock);
> - client->driver->command(client,cmd,arg);
> - mutex_lock(&adap->clist_lock);
> - }
> - module_put(client->driver->driver.owner);
> - }
> - mutex_unlock(&adap->clist_lock);
> + cmd_arg.cmd = cmd;
> + cmd_arg.arg = arg;
> + device_for_each_child(&adap->dev, &cmd_arg, i2c_cmd);
> }
> EXPORT_SYMBOL(i2c_clients_command);
>
> @@ -1151,7 +1136,6 @@ i2c_new_probed_device(struct i2c_adapter
> return NULL;
> }
>
> - mutex_lock(&adap->clist_lock);
> for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
> /* Check address validity */
> if (addr_list[i] < 0x03 || addr_list[i] > 0x77) {
> @@ -1161,7 +1145,7 @@ i2c_new_probed_device(struct i2c_adapter
> }
>
> /* Check address availability */
> - if (__i2c_check_addr(adap, addr_list[i])) {
> + if (i2c_check_addr(adap, addr_list[i])) {
> dev_dbg(&adap->dev, "Address 0x%02x already in "
> "use, not probing\n", addr_list[i]);
> continue;
> @@ -1189,7 +1173,6 @@ i2c_new_probed_device(struct i2c_adapter
> break;
> }
> }
> - mutex_unlock(&adap->clist_lock);
>
> if (addr_list[i] == I2C_CLIENT_END) {
> dev_dbg(&adap->dev, "Probing failed, no device found\n");
> --- a/drivers/i2c/i2c-dev.c
> +++ b/drivers/i2c/i2c-dev.c
> @@ -182,27 +182,26 @@ static ssize_t i2cdev_write (struct file
> return ret;
> }
>
> -/* This address checking function differs from the one in i2c-core
> - in that it considers an address with a registered device, but no
> - bounded driver, as NOT busy. */
> -static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
> +static int i2cdev_check(struct device *dev, void *addrp)
> {
> - struct list_head *item;
> struct i2c_client *client;
> - int res = 0;
>
> - mutex_lock(&adapter->clist_lock);
> - list_for_each(item, &adapter->clients) {
> - client = list_entry(item, struct i2c_client, list);
> - if (client->addr == addr) {
> - if (client->driver)
> - res = -EBUSY;
> - break;
> - }
> - }
> - mutex_unlock(&adapter->clist_lock);
> + if (!dev->bus || strcmp("i2c", dev->bus->name) != 0)
Please just export i2c_bus_type is you need it, or even verify_client
(then renamed to i2c_verify_client)? If the bus type check is really
needed then we'll need it for the 3 v4l drivers I mentioned earlier
anyway.
Alternatively we could write and export an i2c_for_each_client()
function doing all the required checks so that drivers don't have to
care. What do you think?
> + return 0;
>
> - return res;
> + client = to_i2c_client(dev);
> + if (client->addr != *(unsigned int *)addrp)
> + return 0;
> +
> + return dev->driver ? -EBUSY : 0;
> +}
> +
> +/* This address checking function differs from the one in i2c-core
> + in that it considers an address with a registered device, but no
> + driver to it, as NOT busy. */
> +static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
> +{
> + return device_for_each_child(&adapter->dev, &addr, i2cdev_check);
> }
>
> static int i2cdev_ioctl(struct inode *inode, struct file *file,
> --- a/include/linux/i2c.h
> +++ b/include/linux/i2c.h
> @@ -159,7 +159,6 @@ struct i2c_driver {
> * @irq: indicates the IRQ generated by this device (if any)
> * @driver_name: Identifies new-style driver used with this device; also
> * used as the module name for hotplug/coldplug modprobe support.
> - * @list: list of active/busy clients
> * @released: used to synchronize client releases & detaches and references
> *
> * An i2c_client identifies a single device (i.e. chip) connected to an
> @@ -179,7 +178,6 @@ struct i2c_client {
> struct device dev; /* the device structure */
> int irq; /* irq issued by device (or -1) */
> char driver_name[KOBJ_NAME_LEN];
> - struct list_head list;
> struct completion released;
> };
> #define to_i2c_client(d) container_of(d, struct i2c_client, dev)
> @@ -317,14 +315,12 @@ struct i2c_adapter {
> /* data fields that are valid for all devices */
> u8 level; /* nesting level for lockdep */
> struct mutex bus_lock;
> - struct mutex clist_lock;
>
> int timeout;
> int retries;
> struct device dev; /* the adapter device */
>
> int nr;
> - struct list_head clients;
> char name[48];
> struct completion dev_released;
> };
The rest looks OK... nice cleanup, thanks for doing this.
--
Jean Delvare
next prev parent reply other threads:[~2008-01-06 11:23 UTC|newest]
Thread overview: 39+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20071216052308.A0FB11668D7@adsl-69-226-248-13.dsl.pltn13.pacbell.net>
[not found] ` <20071216052308.A0FB11668D7-ZcXrCSuhvln6VZ3dlLfH/g4gEjPzgfUyLrfjE7I9kuVHxeISYlDBzl6hYfS7NtTn@public.gmane.org>
2007-12-27 20:58 ` [patch 2.6.24-rc5-git] add i2c_new_dummy() utility Byron Bradley
[not found] ` <57e2b00712271258l6ea661ai2bfd6b9e099c71be-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2007-12-27 21:53 ` David Brownell
[not found] ` <200712271353.05857.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2007-12-27 22:09 ` Byron Bradley
[not found] ` <57e2b00712271409n6c98f76o45116cd92b01f396-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2007-12-27 23:06 ` David Brownell
[not found] ` <200712271506.43069.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2007-12-27 23:37 ` Byron Bradley
2007-12-27 23:59 ` David Brownell
[not found] ` <200712281230.17840.david-b@pacbell.net>
[not found] ` <57e2b00712281645y70f6ec74s57945dc53f113ec8@mail.gmail.com>
[not found] ` <57e2b00712281645y70f6ec74s57945dc53f113ec8-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2007-12-30 3:05 ` David Brownell
[not found] ` <200712291905.15160.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-04 22:16 ` Jean Delvare
[not found] ` <20080104231633.135f2875-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-04 23:48 ` David Brownell
[not found] ` <200801041548.54825.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-06 9:57 ` Jean Delvare
2008-01-06 11:23 ` Jean Delvare [this message]
[not found] ` <20080106122356.78556b8a-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-06 19:30 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801061130.31774.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-08 14:18 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
[not found] ` <20080108151817.35e05c6c-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-08 16:20 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
[not found] ` <20080108172001.33de6afc-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-08 19:12 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801081112.46972.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-08 20:50 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
[not found] ` <20080108215042.534e32fa-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-08 21:44 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801081344.45544.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-08 22:04 ` i2c-remove-redundant-i2c_client-list.patch Greg KH
[not found] ` <20080108220445.GB3873-U8xfFu+wG4EAvxtiuMwx3w@public.gmane.org>
2008-01-08 22:27 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
2008-01-09 13:29 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
[not found] ` <20080109142906.23a55f5f-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-09 16:19 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
[not found] ` <20080109171934.4f894bdc-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-09 21:21 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801091321.29212.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-10 13:31 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
[not found] ` <20080110143105.456ddaf0-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-14 22:20 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801141420.49274.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-17 22:02 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
2008-01-18 10:14 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
[not found] ` <20080118111401.7ffdccc5-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-18 10:30 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
2008-01-14 22:42 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
2008-01-17 19:35 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801171135.45797.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-17 19:59 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801171159.00291.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-18 9:32 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
[not found] ` <20080118103209.4b92ac76-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2008-01-18 10:16 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801180216.22363.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-18 12:10 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
2008-01-08 18:52 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801081052.31413.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-08 19:57 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
2008-01-09 16:09 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
2008-01-06 19:43 ` i2c-remove-redundant-i2c_client-list.patch David Brownell
[not found] ` <200801061143.34020.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2008-01-08 14:21 ` i2c-remove-redundant-i2c_client-list.patch Jean Delvare
2008-01-17 22:24 ` [patch 2.6.24-rc5-git] add i2c_new_dummy() utility David Brownell
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=20080106122356.78556b8a@hyperion.delvare \
--to=khali-puyad+kwke1g9huczpvpmw@public.gmane.org \
--cc=david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org \
--cc=i2c-GZX6beZjE8VD60Wz+7aTrA@public.gmane.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: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox