diff mbox

[OpenWrt-Devel,procd] hotplug: support for interval commands

Message ID 1430996044-3126-1-git-send-email-zajec5@gmail.com
State Changes Requested
Headers show

Commit Message

Rafał Miłecki May 7, 2015, 10:54 a.m. UTC
This allows executing code with a given interval. As every command, it
can be triggered by any uevent.

Intervals may be useful for counting elapsed time since some action. It
allows e.g. indicating that button has been pressed for some time and
can be already released.

Signed-off-by: Rafał Miłecki <zajec5@gmail.com>
---
Example usage:
	[ "if",
		[ "and",
			[ "eq", "SUBSYSTEM", "button" ],
			[ "eq", "BUTTON", "wps" ],
			[ "eq", "ACTION", "pressed" ],
		],
		[ "set-interval", "wpsbutton", "1000", "/etc/rc.button/%BUTTON%" ]
	],
	[ "if",
		[ "and",
			[ "eq", "SUBSYSTEM", "button" ],
			[ "eq", "BUTTON", "wps" ],
			[ "eq", "ACTION", "released" ],
		],
		[ "clear-interval", "wpsbutton" ]
	],

Result from /etc/rc.button/wps script:
SUBSYSTEM:button BUTTON:wps ACTION:pressed SEEN:34 ELAPSED:
SUBSYSTEM:button BUTTON:wps ACTION:interval SEEN: ELAPSED:1
SUBSYSTEM:button BUTTON:wps ACTION:interval SEEN: ELAPSED:2
SUBSYSTEM:button BUTTON:wps ACTION:interval SEEN: ELAPSED:3
SUBSYSTEM:button BUTTON:wps ACTION:released SEEN:3 ELAPSED:
---
 plug/hotplug.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 143 insertions(+)

Comments

Felix Fietkau May 8, 2015, 2:40 p.m. UTC | #1
On 2015-05-07 12:54, Rafał Miłecki wrote:
> This allows executing code with a given interval. As every command, it
> can be triggered by any uevent.
> 
> Intervals may be useful for counting elapsed time since some action. It
> allows e.g. indicating that button has been pressed for some time and
> can be already released.
> 
> Signed-off-by: Rafał Miłecki <zajec5@gmail.com>
> ---
> Example usage:
> 	[ "if",
> 		[ "and",
> 			[ "eq", "SUBSYSTEM", "button" ],
> 			[ "eq", "BUTTON", "wps" ],
> 			[ "eq", "ACTION", "pressed" ],
> 		],
> 		[ "set-interval", "wpsbutton", "1000", "/etc/rc.button/%BUTTON%" ]
> 	],
> 	[ "if",
> 		[ "and",
> 			[ "eq", "SUBSYSTEM", "button" ],
> 			[ "eq", "BUTTON", "wps" ],
> 			[ "eq", "ACTION", "released" ],
> 		],
> 		[ "clear-interval", "wpsbutton" ]
> 	],
> 
> Result from /etc/rc.button/wps script:
> SUBSYSTEM:button BUTTON:wps ACTION:pressed SEEN:34 ELAPSED:
> SUBSYSTEM:button BUTTON:wps ACTION:interval SEEN: ELAPSED:1
> SUBSYSTEM:button BUTTON:wps ACTION:interval SEEN: ELAPSED:2
> SUBSYSTEM:button BUTTON:wps ACTION:interval SEEN: ELAPSED:3
> SUBSYSTEM:button BUTTON:wps ACTION:released SEEN:3 ELAPSED:
> ---
>  plug/hotplug.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 143 insertions(+)
> 
> diff --git a/plug/hotplug.c b/plug/hotplug.c
> index 1a98e8b..0b183fb 100644
> --- a/plug/hotplug.c
> +++ b/plug/hotplug.c
> @@ -43,7 +43,19 @@ struct cmd_queue {
>  	void (*handler)(struct blob_attr *msg, struct blob_attr *data);
>  };
>  
> +struct cmd_interval {
> +	char *name;
> +	struct list_head list;
> +
> +	struct timespec start;
> +	struct uloop_timeout timeout;
> +
> +	struct blob_attr *msg;
> +	struct blob_attr *data;
> +};
> +
>  static LIST_HEAD(cmd_queue);
> +static LIST_HEAD(cmd_intervals);
I'd suggest using an avl tree for cmd_intervals, so you can use avl_find
for the lookup instead of the open-coded linked list iterate.

>  static struct uloop_process queue_proc;
>  static struct uloop_timeout last_event;
>  static struct blob_buf b;
> @@ -157,6 +169,129 @@ static void handle_exec(struct blob_attr *msg, struct blob_attr *data)
>  	exit(-1);
>  }
>  
> +static void handle_set_interval_timeout(struct uloop_timeout *timeout)
> +{
> +	struct cmd_interval *interval = container_of(timeout, struct cmd_interval, timeout);
> +	struct blob_attr *cur;
> +	char *argv[8];
> +	int rem, fd, pid;
> +	int msecs = 0;
> +	int i = 0;
> +
> +	blobmsg_for_each_attr(cur, interval->data, rem) {
> +		switch (i) {
> +		case 0:
> +			break;
> +		case 1:
> +			msecs = strtol(blobmsg_get_string(cur), NULL, 0);
> +			break;
> +		default:
> +			argv[i - 2] = blobmsg_data(cur);
> +		}
> +		i++;
> +		if (i - 2 == 7)
> +			break;
> +	}
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		perror("fork");
> +	} else if (pid == 0) {
> +		struct timespec now;
> +		char elapsed[6];
> +
> +		if (i - 2 <= 0)
> +			return;
> +
> +		clock_gettime(CLOCK_MONOTONIC, &now);
> +		snprintf(elapsed, sizeof(elapsed), "%ld", now.tv_sec - interval->start.tv_sec);
> +
> +		blobmsg_for_each_attr(cur, interval->msg, rem)
> +			setenv(blobmsg_name(cur), blobmsg_data(cur), 1);
> +		setenv("ACTION", "interval", 1);
> +		setenv("ELAPSED", elapsed, 1);
> +		unsetenv("SEEN");
> +
> +		if (debug < 3) {
> +			fd = open("/dev/null", O_RDWR);
> +			if (fd > -1) {
> +				dup2(fd, STDIN_FILENO);
> +				dup2(fd, STDOUT_FILENO);
> +				dup2(fd, STDERR_FILENO);
> +				if (fd > STDERR_FILENO)
> +					close(fd);
> +			}
> +		}
> +
> +		argv[i - 2] = NULL;
> +		execvp(argv[0], &argv[0]);
> +		exit(-1);
> +	} else {
> +		uloop_timeout_set(&interval->timeout, msecs);
> +	}
> +}
I think this needs protection against hanging scripts. Add a struct
uloop_process to the interval struct to track the pid. Only spawn a new
task if the process has terminated.

> +
> +static void handle_set_interval(struct blob_attr *msg, struct blob_attr *data)
> +{
> +	static struct blobmsg_policy set_interval_policy[2] = {
> +		{ .type = BLOBMSG_TYPE_STRING },
> +		{ .type = BLOBMSG_TYPE_STRING },
> +	};
> +	struct blob_attr *tb[2];
> +	struct cmd_interval *interval;
> +	struct blob_attr *_msg, *_data;
> +	int msecs;
> +
> +	blobmsg_parse_array(set_interval_policy, 2, tb, blobmsg_data(data), blobmsg_data_len(data));
> +	if (!tb[0] || !tb[1])
> +		return;
> +	msecs = strtol(blobmsg_get_string(tb[1]), NULL, 0);
> +
> +	interval = calloc_a(sizeof(struct cmd_interval),
> +		&_msg, blob_pad_len(msg),
> +		&_data, blob_pad_len(data),
> +		NULL);
> +	if (!interval)
> +		return;
> +
> +	interval->name = strdup(blobmsg_get_string(tb[0]));
> +	interval->msg = _msg;
> +	interval->data = _data;
> +	clock_gettime(CLOCK_MONOTONIC, &interval->start);
> +	interval->timeout.cb = handle_set_interval_timeout;
> +
> +	memcpy(interval->msg, msg, blob_pad_len(msg));
> +	memcpy(interval->data, data, blob_pad_len(data));
> +
> +	list_add_tail(&interval->list, &cmd_intervals);
> +
> +	uloop_timeout_set(&interval->timeout, msecs);
> +}
> +
> +static void handle_clear_interval(struct blob_attr *msg, struct blob_attr *data)
> +{
> +	static struct blobmsg_policy clear_interval_policy = {
> +		.type = BLOBMSG_TYPE_STRING,
> +	};
> +	struct blob_attr *tb;
> +	struct cmd_interval *interval;
> +	char *name;
> +
> +	blobmsg_parse_array(&clear_interval_policy, 1, &tb, blobmsg_data(data), blobmsg_data_len(data));
> +	if (!tb)
> +		return;
> +	name = blobmsg_get_string(tb);
> +
> +	list_for_each_entry(interval, &cmd_intervals, list) {
> +		if (!strcmp(interval->name, name)){
> +			uloop_timeout_cancel(&interval->timeout);
> +			list_del(&interval->list);
> +			free(interval);
> +			return;
> +		}
> +	}
> +}
After adding process tracking, you should kill the interval struct only
if the task has exited. If it is still running, mark it as completed (so
no more timers will be issued for it), and free it after the task has
exited.

- Felix
diff mbox

Patch

diff --git a/plug/hotplug.c b/plug/hotplug.c
index 1a98e8b..0b183fb 100644
--- a/plug/hotplug.c
+++ b/plug/hotplug.c
@@ -43,7 +43,19 @@  struct cmd_queue {
 	void (*handler)(struct blob_attr *msg, struct blob_attr *data);
 };
 
+struct cmd_interval {
+	char *name;
+	struct list_head list;
+
+	struct timespec start;
+	struct uloop_timeout timeout;
+
+	struct blob_attr *msg;
+	struct blob_attr *data;
+};
+
 static LIST_HEAD(cmd_queue);
+static LIST_HEAD(cmd_intervals);
 static struct uloop_process queue_proc;
 static struct uloop_timeout last_event;
 static struct blob_buf b;
@@ -157,6 +169,129 @@  static void handle_exec(struct blob_attr *msg, struct blob_attr *data)
 	exit(-1);
 }
 
+static void handle_set_interval_timeout(struct uloop_timeout *timeout)
+{
+	struct cmd_interval *interval = container_of(timeout, struct cmd_interval, timeout);
+	struct blob_attr *cur;
+	char *argv[8];
+	int rem, fd, pid;
+	int msecs = 0;
+	int i = 0;
+
+	blobmsg_for_each_attr(cur, interval->data, rem) {
+		switch (i) {
+		case 0:
+			break;
+		case 1:
+			msecs = strtol(blobmsg_get_string(cur), NULL, 0);
+			break;
+		default:
+			argv[i - 2] = blobmsg_data(cur);
+		}
+		i++;
+		if (i - 2 == 7)
+			break;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		perror("fork");
+	} else if (pid == 0) {
+		struct timespec now;
+		char elapsed[6];
+
+		if (i - 2 <= 0)
+			return;
+
+		clock_gettime(CLOCK_MONOTONIC, &now);
+		snprintf(elapsed, sizeof(elapsed), "%ld", now.tv_sec - interval->start.tv_sec);
+
+		blobmsg_for_each_attr(cur, interval->msg, rem)
+			setenv(blobmsg_name(cur), blobmsg_data(cur), 1);
+		setenv("ACTION", "interval", 1);
+		setenv("ELAPSED", elapsed, 1);
+		unsetenv("SEEN");
+
+		if (debug < 3) {
+			fd = open("/dev/null", O_RDWR);
+			if (fd > -1) {
+				dup2(fd, STDIN_FILENO);
+				dup2(fd, STDOUT_FILENO);
+				dup2(fd, STDERR_FILENO);
+				if (fd > STDERR_FILENO)
+					close(fd);
+			}
+		}
+
+		argv[i - 2] = NULL;
+		execvp(argv[0], &argv[0]);
+		exit(-1);
+	} else {
+		uloop_timeout_set(&interval->timeout, msecs);
+	}
+}
+
+static void handle_set_interval(struct blob_attr *msg, struct blob_attr *data)
+{
+	static struct blobmsg_policy set_interval_policy[2] = {
+		{ .type = BLOBMSG_TYPE_STRING },
+		{ .type = BLOBMSG_TYPE_STRING },
+	};
+	struct blob_attr *tb[2];
+	struct cmd_interval *interval;
+	struct blob_attr *_msg, *_data;
+	int msecs;
+
+	blobmsg_parse_array(set_interval_policy, 2, tb, blobmsg_data(data), blobmsg_data_len(data));
+	if (!tb[0] || !tb[1])
+		return;
+	msecs = strtol(blobmsg_get_string(tb[1]), NULL, 0);
+
+	interval = calloc_a(sizeof(struct cmd_interval),
+		&_msg, blob_pad_len(msg),
+		&_data, blob_pad_len(data),
+		NULL);
+	if (!interval)
+		return;
+
+	interval->name = strdup(blobmsg_get_string(tb[0]));
+	interval->msg = _msg;
+	interval->data = _data;
+	clock_gettime(CLOCK_MONOTONIC, &interval->start);
+	interval->timeout.cb = handle_set_interval_timeout;
+
+	memcpy(interval->msg, msg, blob_pad_len(msg));
+	memcpy(interval->data, data, blob_pad_len(data));
+
+	list_add_tail(&interval->list, &cmd_intervals);
+
+	uloop_timeout_set(&interval->timeout, msecs);
+}
+
+static void handle_clear_interval(struct blob_attr *msg, struct blob_attr *data)
+{
+	static struct blobmsg_policy clear_interval_policy = {
+		.type = BLOBMSG_TYPE_STRING,
+	};
+	struct blob_attr *tb;
+	struct cmd_interval *interval;
+	char *name;
+
+	blobmsg_parse_array(&clear_interval_policy, 1, &tb, blobmsg_data(data), blobmsg_data_len(data));
+	if (!tb)
+		return;
+	name = blobmsg_get_string(tb);
+
+	list_for_each_entry(interval, &cmd_intervals, list) {
+		if (!strcmp(interval->name, name)){
+			uloop_timeout_cancel(&interval->timeout);
+			list_del(&interval->list);
+			free(interval);
+			return;
+		}
+	}
+}
+
 static void handle_firmware(struct blob_attr *msg, struct blob_attr *data)
 {
 	char *dir = blobmsg_get_string(blobmsg_data(data));
@@ -254,6 +389,14 @@  static struct cmd_handler {
 		.name = "exec",
 		.handler = handle_exec,
 	}, {
+		.name = "set-interval",
+		.atomic = 1,
+		.handler = handle_set_interval,
+	}, {
+		.name = "clear-interval",
+		.atomic = 1,
+		.handler = handle_clear_interval,
+	}, {
 		.name = "load-firmware",
 		.handler = handle_firmware,
 	},