diff mbox series

[15/18] ledtrig-blkdev: Add sysfs attributes to [un]link LEDs & devices

Message ID 20210903204548.2745354-16-arequipeno@gmail.com
State New
Headers show
Series Introduce block device LED trigger | expand

Commit Message

Ian Pilcher Sept. 3, 2021, 8:45 p.m. UTC
/sys/class/leds/<led>/add_blkdev - to create device/LED associations

/sys/class/leds/<led>/delete_blkdev to remove device/LED associations

For both attributes, accept multiple device names separated by whitespace

Signed-off-by: Ian Pilcher <arequipeno@gmail.com>
---
 drivers/leds/trigger/ledtrig-blkdev.c | 48 +++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

Comments

Greg Kroah-Hartman Sept. 4, 2021, 5:57 a.m. UTC | #1
On Fri, Sep 03, 2021 at 03:45:45PM -0500, Ian Pilcher wrote:
> /sys/class/leds/<led>/add_blkdev - to create device/LED associations

> 

> /sys/class/leds/<led>/delete_blkdev to remove device/LED associations

> 

> For both attributes, accept multiple device names separated by whitespace

> 

> Signed-off-by: Ian Pilcher <arequipeno@gmail.com>

> ---

>  drivers/leds/trigger/ledtrig-blkdev.c | 48 +++++++++++++++++++++++++++

>  1 file changed, 48 insertions(+)


Where are the Documentation/ABI/ updates for these new sysfs files?

thanks,

greg k-h
Greg Kroah-Hartman Sept. 4, 2021, 5:59 a.m. UTC | #2
On Fri, Sep 03, 2021 at 03:45:45PM -0500, Ian Pilcher wrote:
> /sys/class/leds/<led>/add_blkdev - to create device/LED associations

> 

> /sys/class/leds/<led>/delete_blkdev to remove device/LED associations

> 

> For both attributes, accept multiple device names separated by whitespace

> 

> Signed-off-by: Ian Pilcher <arequipeno@gmail.com>

> ---

>  drivers/leds/trigger/ledtrig-blkdev.c | 48 +++++++++++++++++++++++++++

>  1 file changed, 48 insertions(+)

> 

> diff --git a/drivers/leds/trigger/ledtrig-blkdev.c b/drivers/leds/trigger/ledtrig-blkdev.c

> index b2ec85b805d0..db82d37fc721 100644

> --- a/drivers/leds/trigger/ledtrig-blkdev.c

> +++ b/drivers/leds/trigger/ledtrig-blkdev.c

> @@ -509,3 +509,51 @@ static void blkdev_deactivate(struct led_classdev *const led_dev)

>  

>  	module_put(THIS_MODULE);

>  }

> +

> +

> +/*

> + *

> + *	sysfs attributes to add & delete devices from LEDs

> + *

> + */

> +

> +static ssize_t blkdev_add_or_del(struct device *const dev,

> +				 struct device_attribute *const attr,

> +				 const char *const buf, const size_t count);

> +

> +static struct device_attribute ledtrig_blkdev_attr_add =

> +	__ATTR(add_blkdev, 0200, NULL, blkdev_add_or_del);

> +

> +static struct device_attribute ledtrig_blkdev_attr_del =

> +	__ATTR(delete_blkdev, 0200, NULL, blkdev_add_or_del);


DEVICE_ATTR_RO()?  Or something like that?  Do not use __ATTR() for
device attributes if at all possible, worst case, use DEVICE_ATTR()
here.

And the mode settings are odd, are you sure you want that?

> +static ssize_t blkdev_add_or_del(struct device *const dev,

> +				 struct device_attribute *const attr,

> +				 const char *const buf, const size_t count)

> +{

> +	struct ledtrig_blkdev_led *const led = led_trigger_get_drvdata(dev);

> +	const char *const disk_name = blkdev_skip_space(buf);

> +	const char *const endp = blkdev_find_space(disk_name);

> +	const ptrdiff_t name_len = endp - disk_name;	/* always >= 0 */

> +	int ret;

> +

> +	if (name_len == 0) {

> +		pr_info("blkdev LED: empty block device name\n");


Looks like debugging code, please remove.

And how can this ever happen?

> +		return -EINVAL;

> +	}

> +

> +	if (attr == &ledtrig_blkdev_attr_del) {

> +		blkdev_disk_delete(led, disk_name, name_len);

> +	} else {	/* attr == &ledtrig_blkdev_attr_add */

> +		ret = blkdev_disk_add(led, disk_name, name_len);


Why do you have a single attribute callback that does two totally
different things?  Just have 2 different callback functions please, it
makes things much easier to review and maintain over time.

thanks,

greg k-h
Ian Pilcher Sept. 4, 2021, 9:28 p.m. UTC | #3
On 9/4/21 12:57 AM, Greg KH wrote:
> Where are the Documentation/ABI/ updates for these new sysfs files?


They're in Documentation/ABI/testing/sysfs-class-led-trigger-blkdev,
which is added in the first patch in the series.

-- 
========================================================================
                  In Soviet Russia, Google searches you!
========================================================================
Ian Pilcher Sept. 4, 2021, 10:35 p.m. UTC | #4
On 9/4/21 12:59 AM, Greg KH wrote:
> DEVICE_ATTR_RO()?  Or something like that?  Do not use __ATTR() for

> device attributes if at all possible, worst case, use DEVICE_ATTR()

> here.


For some reason, it didn't click until now that these are device
attributes (because I was focused on the fact that I was working on the
LED trigger).

DEVICE_ATTR*() it is.

> And the mode settings are odd, are you sure you want that?


Yes.  These are write-only attributes.

>> +	if (name_len == 0) {

>> +		pr_info("blkdev LED: empty block device name\n");

> 

> Looks like debugging code, please remove.


It's really more of an error message for the system administrator.  So
as with my earlier note, dev_info() would be my preference.

> And how can this ever happen?


The blkdev_skip_space() and blkdev_find_space() calls effectively find
the first non-whitespace token in the buffer (disk_name) and its length
(name_len).  If the buffer only contains whitespace (e.g. echo > $ATTR),
then name_len will be 0.

> 

>> +		return -EINVAL;

>> +	}

>> +

>> +	if (attr == &ledtrig_blkdev_attr_del) {

>> +		blkdev_disk_delete(led, disk_name, name_len);

>> +	} else {	/* attr == &ledtrig_blkdev_attr_add */

>> +		ret = blkdev_disk_add(led, disk_name, name_len);

> 

> Why do you have a single attribute callback that does two totally

> different things?  Just have 2 different callback functions please, it

> makes things much easier to review and maintain over time.


Hmmm.  All of the "real work" is done in blkdev_disk_delete() and
blkdev_disk_add().  The store function's only purpose is to parse the
token(s) from the buffer, and that logic is exactly the same for the
two different attributes.

So it's a choice between:

> static ssize_t blkdev_add_or_del(struct device *const dev,

> 				 struct device_attribute *const attr,

> 				 const char *const buf, const size_t count)

> {

> 	struct ledtrig_blkdev_led *const led = led_trigger_get_drvdata(dev);

> 	const char *const disk_name = blkdev_skip_space(buf);

> 	const char *const endp = blkdev_find_space(disk_name);

> 	const ptrdiff_t name_len = endp - disk_name;	/* always >= 0 */

> 	int ret;

> 

> 	if (name_len == 0) {

> 		pr_info("blkdev LED: empty block device name\n");

> 		return -EINVAL;

> 	}

> 

> 	if (attr == &ledtrig_blkdev_attr_del) {

> 		blkdev_disk_delete(led, disk_name, name_len);

> 	} else {	/* attr == &ledtrig_blkdev_attr_add */

> 		ret = blkdev_disk_add(led, disk_name, name_len);

> 		if (ret != 0)

> 			return ret;

> 	}

> 

> 	/*

> 	 * Consume everything up to the next non-whitespace token (or the end

> 	 * of the input).  Avoids "empty block device name" error if there is

> 	 * whitespace (such as a newline) after the last token.

> 	 */

> 	return blkdev_skip_space(endp) - buf;

> }


Or:

> static ssize_t blkdev_add(struct device *const dev,

> 			  struct device_attribute *const attr,

> 			  const char *const buf, const size_t count)

> {

> 	struct ledtrig_blkdev_led *const led = led_trigger_get_drvdata(dev);

> 	const char *const disk_name = blkdev_skip_space(buf);

> 	const char *const endp = blkdev_find_space(disk_name);

> 	const ptrdiff_t name_len = endp - disk_name;	/* always >= 0 */

> 	int ret;

> 

> 	if (name_len == 0) {

> 		pr_info("blkdev LED: empty block device name\n");

> 		return -EINVAL;

> 	}

> 

> 	ret = blkdev_disk_add(led, disk_name, name_len);

> 	if (ret != 0)

> 		return ret;

> 

> 	/*

> 	 * Consume everything up to the next non-whitespace token (or the end

> 	 * of the input).  Avoids "empty block device name" error if there is

> 	 * whitespace (such as a newline) after the last token.

> 	 */

> 	return blkdev_skip_space(endp) - buf;

> }

> 

> 

> static ssize_t blkdev_del(struct device *const dev,

> 			  struct device_attribute *const attr,

> 			  const char *const buf, const size_t count)

> {

> 	struct ledtrig_blkdev_led *const led = led_trigger_get_drvdata(dev);

> 	const char *const disk_name = blkdev_skip_space(buf);

> 	const char *const endp = blkdev_find_space(disk_name);

> 	const ptrdiff_t name_len = endp - disk_name;	/* always >= 0 */

> 	int ret;

> 

> 	if (name_len == 0) {

> 		pr_info("blkdev LED: empty block device name\n");

> 		return -EINVAL;

> 	}

> 

> 	blkdev_disk_delete(led, disk_name, name_len);

> 

> 	/* See comment in blkdev_add() */

> 	return blkdev_skip_space(endp) - buf;

> }


Maybe the right thing to do is to simply add a comment to clarify the
separation (and maybe rename the function as well)?

Thanks!

-- 
========================================================================
                  In Soviet Russia, Google searches you!
========================================================================
Greg Kroah-Hartman Sept. 5, 2021, 2:51 p.m. UTC | #5
On Sat, Sep 04, 2021 at 05:35:29PM -0500, Ian Pilcher wrote:
> On 9/4/21 12:59 AM, Greg KH wrote:

> > DEVICE_ATTR_RO()?  Or something like that?  Do not use __ATTR() for

> > device attributes if at all possible, worst case, use DEVICE_ATTR()

> > here.

> 

> For some reason, it didn't click until now that these are device

> attributes (because I was focused on the fact that I was working on the

> LED trigger).

> 

> DEVICE_ATTR*() it is.

> 

> > And the mode settings are odd, are you sure you want that?

> 

> Yes.  These are write-only attributes.


DEVICE_ATTR_WO() then please.

> > > +	if (name_len == 0) {

> > > +		pr_info("blkdev LED: empty block device name\n");

> > 

> > Looks like debugging code, please remove.

> 

> It's really more of an error message for the system administrator.  So

> as with my earlier note, dev_info() would be my preference.


Nope, dev_err() for real errors please.

> > And how can this ever happen?

> 

> The blkdev_skip_space() and blkdev_find_space() calls effectively find

> the first non-whitespace token in the buffer (disk_name) and its length

> (name_len).  If the buffer only contains whitespace (e.g. echo > $ATTR),

> then name_len will be 0.


That's a crazy interface, as others pointed out, don't do that please.

thanks,

greg k-h
Ian Pilcher Sept. 5, 2021, 3:33 p.m. UTC | #6
On 9/5/21 9:51 AM, Greg KH wrote:
>> It's really more of an error message for the system administrator.  So

>> as with my earlier note, dev_info() would be my preference.

> 

> Nope, dev_err() for real errors please.


Just to clarify, the error in this case is the system administrator
writing an incorrect value to a sysfs attribute (likely via a udev
rule), i.e. a "pilot error."

One of the reviewers of one of my RFC patch sets commented that those
should be INFO level at most.

So dev_err() or dev_info() for that sort of thing (always given that
only the root user has permission to write to trigger the error
message)?

>> The blkdev_skip_space() and blkdev_find_space() calls effectively find

>> the first non-whitespace token in the buffer (disk_name) and its length

>> (name_len).  If the buffer only contains whitespace (e.g. echo > $ATTR),

>> then name_len will be 0.

> 

> That's a crazy interface, as others pointed out, don't do that please.


As Pavel noted, it would be ideal to use symlink()/unlink() in the LED's
block_devices directory for this.  As far as I know however, sysfs
doesn't support doing that.  I'd be happy to learn otherwise.  I would
also welcome any other suggestions for a better interface for setting up
the many-to-many relationships that the trigger supports.

That said, I don't know what that has to do with blkdev_skip_space() and
blkdev_find_space(), which are just helper functions that I use to parse
the device name out of the buffer passed to the store function.
Ultimately, the store function does need to handle the case where the
system administrator (or a broken udev rule) writes an all-whitespace
string to the attribute.

I will try to restructure the code to make things more clear.

Thanks!

-- 
========================================================================
                  In Soviet Russia, Google searches you!
========================================================================
Greg Kroah-Hartman Sept. 5, 2021, 4:43 p.m. UTC | #7
On Sun, Sep 05, 2021 at 10:33:08AM -0500, Ian Pilcher wrote:
> On 9/5/21 9:51 AM, Greg KH wrote:

> > > It's really more of an error message for the system administrator.  So

> > > as with my earlier note, dev_info() would be my preference.

> > 

> > Nope, dev_err() for real errors please.

> 

> Just to clarify, the error in this case is the system administrator

> writing an incorrect value to a sysfs attribute (likely via a udev

> rule), i.e. a "pilot error."

> 

> One of the reviewers of one of my RFC patch sets commented that those

> should be INFO level at most.

> 

> So dev_err() or dev_info() for that sort of thing (always given that

> only the root user has permission to write to trigger the error

> message)?


Really you should not have any kernel log messages for invalid data sent
to a sysfs file, just return -EINVAL and be done with it.

> > > The blkdev_skip_space() and blkdev_find_space() calls effectively find

> > > the first non-whitespace token in the buffer (disk_name) and its length

> > > (name_len).  If the buffer only contains whitespace (e.g. echo > $ATTR),

> > > then name_len will be 0.

> > 

> > That's a crazy interface, as others pointed out, don't do that please.

> 

> As Pavel noted, it would be ideal to use symlink()/unlink() in the LED's

> block_devices directory for this.  As far as I know however, sysfs

> doesn't support doing that.  I'd be happy to learn otherwise.  I would

> also welcome any other suggestions for a better interface for setting up

> the many-to-many relationships that the trigger supports.


sysfs does not allow that as that is not what sysfs is for.  Perhaps you
want to use configfs, as that is exactly what that is for.

> That said, I don't know what that has to do with blkdev_skip_space() and

> blkdev_find_space(), which are just helper functions that I use to parse

> the device name out of the buffer passed to the store function.

> Ultimately, the store function does need to handle the case where the

> system administrator (or a broken udev rule) writes an all-whitespace

> string to the attribute.


Handling invalid data is fine, but having to parse multiple values in a
single sysfs file violates the rules of sysfs.  Please use something
else instead.

thanks,

greg k-h
diff mbox series

Patch

diff --git a/drivers/leds/trigger/ledtrig-blkdev.c b/drivers/leds/trigger/ledtrig-blkdev.c
index b2ec85b805d0..db82d37fc721 100644
--- a/drivers/leds/trigger/ledtrig-blkdev.c
+++ b/drivers/leds/trigger/ledtrig-blkdev.c
@@ -509,3 +509,51 @@  static void blkdev_deactivate(struct led_classdev *const led_dev)
 
 	module_put(THIS_MODULE);
 }
+
+
+/*
+ *
+ *	sysfs attributes to add & delete devices from LEDs
+ *
+ */
+
+static ssize_t blkdev_add_or_del(struct device *const dev,
+				 struct device_attribute *const attr,
+				 const char *const buf, const size_t count);
+
+static struct device_attribute ledtrig_blkdev_attr_add =
+	__ATTR(add_blkdev, 0200, NULL, blkdev_add_or_del);
+
+static struct device_attribute ledtrig_blkdev_attr_del =
+	__ATTR(delete_blkdev, 0200, NULL, blkdev_add_or_del);
+
+static ssize_t blkdev_add_or_del(struct device *const dev,
+				 struct device_attribute *const attr,
+				 const char *const buf, const size_t count)
+{
+	struct ledtrig_blkdev_led *const led = led_trigger_get_drvdata(dev);
+	const char *const disk_name = blkdev_skip_space(buf);
+	const char *const endp = blkdev_find_space(disk_name);
+	const ptrdiff_t name_len = endp - disk_name;	/* always >= 0 */
+	int ret;
+
+	if (name_len == 0) {
+		pr_info("blkdev LED: empty block device name\n");
+		return -EINVAL;
+	}
+
+	if (attr == &ledtrig_blkdev_attr_del) {
+		blkdev_disk_delete(led, disk_name, name_len);
+	} else {	/* attr == &ledtrig_blkdev_attr_add */
+		ret = blkdev_disk_add(led, disk_name, name_len);
+		if (ret != 0)
+			return ret;
+	}
+
+	/*
+	 * Consume everything up to the next non-whitespace token (or the end
+	 * of the input).  Avoids "empty block device name" error if there is
+	 * whitespace (such as a newline) after the last token.
+	 */
+	return blkdev_skip_space(endp) - buf;
+}