diff mbox series

[30/35] multipathd: uxlsnr: merge uxsock_trigger() into state machine

Message ID 20210910114120.13665-31-mwilck@suse.com (mailing list archive)
State Not Applicable, archived
Delegated to: christophe varoqui
Headers show
Series multipathd: uxlsnr overhaul | expand

Commit Message

Martin Wilck Sept. 10, 2021, 11:41 a.m. UTC
From: Martin Wilck <mwilck@suse.com>

This patch sets up the bulk of the state machine. The idea is to
fall through the case labels as long as possible (when steps succeed)
and return to the caller if either an error occurs, or it becomes
necessary to wait for some pollable condition.

While doing this, switch to negative error codes for the functions
in uxlsnr.c (e.g. parse_cmd()). Positive return codes are reserved
for the cli_handler functions themselves. This way we can clearly
distinguish the error source, and avoid confusion and misleading
error messages. No cli_handler returns negative values.

Note: with this patch applied, clients may hang and time out if
the handler fails to acquire the vecs lock. This will be fixed in the
follow-up patch "multipathd: uxlsnr: add idle notification".

Signed-off-by: Martin Wilck <mwilck@suse.com>
---
 multipathd/uxlsnr.c | 145 ++++++++++++++++++++++++--------------------
 1 file changed, 80 insertions(+), 65 deletions(-)

Comments

Benjamin Marzinski Sept. 16, 2021, 3:32 a.m. UTC | #1
On Fri, Sep 10, 2021 at 01:41:15PM +0200, mwilck@suse.com wrote:
> From: Martin Wilck <mwilck@suse.com>
> 
> This patch sets up the bulk of the state machine. The idea is to
> fall through the case labels as long as possible (when steps succeed)
> and return to the caller if either an error occurs, or it becomes
> necessary to wait for some pollable condition.
> 
> While doing this, switch to negative error codes for the functions
> in uxlsnr.c (e.g. parse_cmd()). Positive return codes are reserved
> for the cli_handler functions themselves. This way we can clearly
> distinguish the error source, and avoid confusion and misleading
> error messages. No cli_handler returns negative values.
> 
> Note: with this patch applied, clients may hang and time out if
> the handler fails to acquire the vecs lock. This will be fixed in the
> follow-up patch "multipathd: uxlsnr: add idle notification".
> 
> Signed-off-by: Martin Wilck <mwilck@suse.com>
> ---
>  multipathd/uxlsnr.c | 145 ++++++++++++++++++++++++--------------------
>  1 file changed, 80 insertions(+), 65 deletions(-)
> 
> diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c
> index ff9604f..553274b 100644
> --- a/multipathd/uxlsnr.c
> +++ b/multipathd/uxlsnr.c
> @@ -299,22 +299,13 @@ static int parse_cmd(struct client *c)
>  
>  	r = get_cmdvec(c->cmd, &c->cmdvec);
>  
> -	if (r) {
> -		genhelp_handler(c->cmd, r, &c->reply);
> -		if (get_strbuf_len(&c->reply) == 0)
> -			return EINVAL;
> -		return 0;
> -	}
> +	if (r)
> +		return -r;
>  
>  	c->handler = find_handler_for_cmdvec(c->cmdvec);
>  
> -	if (!c->handler || !c->handler->fn) {
> -		genhelp_handler(c->cmd, EINVAL, &c->reply);
> -		if (get_strbuf_len(&c->reply) == 0)
> -			r = EINVAL;
> -		else
> -			r = 0;
> -	}
> +	if (!c->handler || !c->handler->fn)
> +		return -EINVAL;
>  
>  	return r;
>  }
> @@ -325,7 +316,7 @@ static int execute_handler(struct client *c, struct vectors *vecs, int timeout)
>  	struct timespec tmo;
>  
>  	if (!c->handler)
> -		return EINVAL;
> +		return -EINVAL;
>  
>  	if (clock_gettime(CLOCK_REALTIME, &tmo) == 0) {
>  		tmo.tv_sec += timeout;
> @@ -355,50 +346,29 @@ static int execute_handler(struct client *c, struct vectors *vecs, int timeout)
>  	return r;
>  }
>  
> -static int uxsock_trigger(struct client *c, void *trigger_data)
> +void default_reply(struct client *c, int r)
>  {
> -	struct vectors * vecs;
> -	int r = 1;
> -
> -	vecs = (struct vectors *)trigger_data;
> -
> -	r = parse_cmd(c);
> -
> -	if (r == 0 && c->cmdvec && VECTOR_SIZE(c->cmdvec) > 0) {
> -		struct key *kw = VECTOR_SLOT(c->cmdvec, 0);
> -
> -		if (!c->is_root && kw->code != LIST)
> -			r = EPERM;
> -	}
> -
> -	if (r == 0 && c->handler)
> -		r = execute_handler(c, vecs, uxsock_timeout / 1000);
> -
> -	if (c->cmdvec) {
> -		free_keys(c->cmdvec);
> -		c->cmdvec = NULL;
> -	}
> -
> -	if (r > 0) {
> -		switch(r) {
> -		case ETIMEDOUT:
> -			append_strbuf_str(&c->reply, "timeout\n");
> -			break;
> -		case EPERM:
> -			append_strbuf_str(&c->reply,
> -					  "permission deny: need to be root\n");
> -			break;
> -		default:
> -			append_strbuf_str(&c->reply, "fail\n");
> -			break;
> -		}
> -	}
> -	else if (!r && get_strbuf_len(&c->reply) == 0) {
> +	switch(r) {
> +	case -EINVAL:
> +	case -ESRCH:
> +	case -ENOMEM:

get_cmdvec() returns positive errors and do_genhelp() expects positive
errors, but this expects negative errors.

> +		/* return codes from get_cmdvec() */
> +		genhelp_handler(c->cmd, r, &c->reply);
> +		break;
> +	case -EPERM:
> +		append_strbuf_str(&c->reply,
> +				  "permission deny: need to be root\n");
> +		break;
> +	case -ETIMEDOUT:
> +		append_strbuf_str(&c->reply, "timeout\n");
> +		break;
> +	case 0:
>  		append_strbuf_str(&c->reply, "ok\n");
> -		r = 0;
> +		break;
> +	default:
> +		append_strbuf_str(&c->reply, "fail\n");
> +		break;
>  	}
> -	/* else if (r < 0) leave *reply alone */
> -	return r;
>  }
>  
>  static void set_client_state(struct client *c, int state)
> @@ -409,6 +379,7 @@ static void set_client_state(struct client *c, int state)
>  		reset_strbuf(&c->reply);
>  		memset(c->cmd, '\0', sizeof(c->cmd));
>  		c->expires = ts_zero;
> +		c->error = 0;
>  		/* fallthrough */
>  	case CLT_SEND:
>  		/* reuse these fields for next data transfer */
> @@ -420,10 +391,13 @@ static void set_client_state(struct client *c, int state)
>  	c->state = state;
>  }
>  
> -static void handle_client(struct client *c, void *trigger_data)
> +static void handle_client(struct client *c, struct vectors *vecs)
>  {
>  	ssize_t n;
>  
> +	condlog(4, "%s: cli[%d] state=%d cmd=\"%s\" repl \"%s\"", __func__,
> +		c->fd, c->state, c->cmd, get_strbuf_str(&c->reply));
> +
>  	switch (c->state) {
>  	case CLT_RECV:
>  		if (c->cmd_len == 0) {
> @@ -464,15 +438,52 @@ static void handle_client(struct client *c, void *trigger_data)
>  				return;
>  			set_client_state(c, CLT_PARSE);
>  		}
> -		break;
> -	default:
> -		break;
> -	}
> +		condlog(4, "cli[%d]: Got request [%s]", c->fd, c->cmd);
> +		/* fallthrough */
>  
> -	condlog(4, "cli[%d]: Got request [%s]", c->fd, c->cmd);
> -	uxsock_trigger(c, trigger_data);
> +	case CLT_PARSE:

Since you already print this information at the top of the function, it
seems liek it would make more sense to put these prints before the
followthroughs, so that they get double printed immediately when the
function is called for devices not in CLT_RECV.

> +		condlog(4, "%s: cli[%d] state=%d cmd=\"%s\" repl \"%s\"", __func__,
> +			c->fd, c->state, c->cmd,  get_strbuf_str(&c->reply));
> +		c->error = parse_cmd(c);
> +
> +		/* Permission check */
> +		if (c->error == 0 && c->cmdvec && VECTOR_SIZE(c->cmdvec) > 0) {
> +			struct key *kw = VECTOR_SLOT(c->cmdvec, 0);
> +
> +			if (!c->is_root && kw->code != LIST) {
> +				/* this will fall through to CLT_SEND */
> +				c->error = -EPERM;
> +				condlog(0, "%s: cli[%d]: unauthorized cmd \"%s\"",
> +					__func__, c->fd, c->cmd);
> +			}
> +		}
> +		set_client_state(c, CLT_WAIT_LOCK);

I don't have strong feelings about this, but this state machine doesn't
want to always fall through. sometimes, like if you get -EPERM, you want
to swith from CLT_PARSE to CLT_SEND.  If instead of fallthroughs, you
just put the switch statement in a loop, and simply returned when you
wanted to break out to uxsock_listen, you could jump from any state to
any other state, and wouldn't need to have code to skip the actions of
some states, to enable the follow throughs. Just a thought.

-Ben

> +		/* fallthrough */
> +
> +	case CLT_WAIT_LOCK:
> +		condlog(4, "%s: cli[%d] state=%d cmd=\"%s\" repl \"%s\"", __func__,
> +			c->fd, c->state, c->cmd, get_strbuf_str(&c->reply));
> +		/* tbd */
> +		set_client_state(c, CLT_WORK);
> +		/* fallthrough */
> +
> +	case CLT_WORK:
> +		condlog(4, "%s: cli[%d] state=%d cmd=\"%s\" repl \"%s\"", __func__,
> +			c->fd, c->state, c->cmd, get_strbuf_str(&c->reply));
> +		if (c->error == 0 && c->handler)
> +			c->error = execute_handler(c, vecs, uxsock_timeout / 1000);
> +
> +		if (c->cmdvec) {
> +			free_keys(c->cmdvec);
> +			c->cmdvec = NULL;
> +		}
> +		set_client_state(c, CLT_SEND);
> +		/* fallthrough */
> +
> +	case CLT_SEND:
> +		if (get_strbuf_len(&c->reply) == 0)
> +			default_reply(c, c->error);
>  
> -	if (get_strbuf_len(&c->reply) > 0) {
>  		const char *buf = get_strbuf_str(&c->reply);
>  
>  		if (send_packet(c->fd, buf) != 0)
> @@ -481,9 +492,13 @@ static void handle_client(struct client *c, void *trigger_data)
>  			condlog(4, "cli[%d]: Reply [%zu bytes]", c->fd,
>  				get_strbuf_len(&c->reply) + 1);
>  		reset_strbuf(&c->reply);
> -	}
>  
> -	set_client_state(c, CLT_RECV);
> +		set_client_state(c, CLT_RECV);
> +		break;
> +
> +	default:
> +		break;
> +	}
>  }
>  
>  /*
> -- 
> 2.33.0

--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Martin Wilck Sept. 16, 2021, 8:02 a.m. UTC | #2
On Wed, 2021-09-15 at 22:32 -0500, Benjamin Marzinski wrote:
> On Fri, Sep 10, 2021 at 01:41:15PM +0200, mwilck@suse.com wrote:
> > From: Martin Wilck <mwilck@suse.com>
> > 
> > This patch sets up the bulk of the state machine. The idea is to
> > fall through the case labels as long as possible (when steps
> > succeed)
> > and return to the caller if either an error occurs, or it becomes
> > necessary to wait for some pollable condition.
> > 
> > While doing this, switch to negative error codes for the functions
> > in uxlsnr.c (e.g. parse_cmd()). Positive return codes are reserved
> > for the cli_handler functions themselves. This way we can clearly
> > distinguish the error source, and avoid confusion and misleading
> > error messages. No cli_handler returns negative values.
> > 
> > Note: with this patch applied, clients may hang and time out if
> > the handler fails to acquire the vecs lock. This will be fixed in
> > the
> > follow-up patch "multipathd: uxlsnr: add idle notification".
> > 
> > Signed-off-by: Martin Wilck <mwilck@suse.com>
> > ---
> >  multipathd/uxlsnr.c | 145 ++++++++++++++++++++++++----------------
> > ----
> >  1 file changed, 80 insertions(+), 65 deletions(-)
> > 
> > 

> > +               set_client_state(c, CLT_WAIT_LOCK);
> 
> I don't have strong feelings about this, but this state machine
> doesn't
> want to always fall through. sometimes, like if you get -EPERM, you
> want
> to swith from CLT_PARSE to CLT_SEND.  If instead of fallthroughs, you
> just put the switch statement in a loop, and simply returned when you
> wanted to break out to uxsock_listen, you could jump from any state
> to
> any other state, and wouldn't need to have code to skip the actions
> of
> some states, to enable the follow throughs. Just a thought.

Good point. I'll give it a shot and see how it plays out. The end
result will probably be better readable.

I'll also work on your other points.

Regards
Martin



--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
Martin Wilck Nov. 12, 2021, 10:07 p.m. UTC | #3
On Wed, 2021-09-15 at 22:32 -0500, Benjamin Marzinski wrote:
> On Fri, Sep 10, 2021 at 01:41:15PM +0200, mwilck@suse.com wrote:
> > From: Martin Wilck <mwilck@suse.com>
> > 
> > This patch sets up the bulk of the state machine. The idea is to
> > fall through the case labels as long as possible (when steps
> > succeed)
> > and return to the caller if either an error occurs, or it becomes
> > necessary to wait for some pollable condition.
> > 
> > While doing this, switch to negative error codes for the functions
> > in uxlsnr.c (e.g. parse_cmd()). Positive return codes are reserved
> > for the cli_handler functions themselves. This way we can clearly
> > distinguish the error source, and avoid confusion and misleading
> > error messages. No cli_handler returns negative values.
> > 
> > Note: with this patch applied, clients may hang and time out if
> > the handler fails to acquire the vecs lock. This will be fixed in
> > the
> > follow-up patch "multipathd: uxlsnr: add idle notification".
> > 
> > Signed-off-by: Martin Wilck <mwilck@suse.com>
> > ---
> >  multipathd/uxlsnr.c | 145 ++++++++++++++++++++++++----------------
> > ----
> >  1 file changed, 80 insertions(+), 65 deletions(-)
> > 
> > diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c
> > index ff9604f..553274b 100644
> > --- a/multipathd/uxlsnr.c
> > +++ b/multipathd/uxlsnr.c
> >  
> > -static int uxsock_trigger(struct client *c, void *trigger_data)
> > +void default_reply(struct client *c, int r)
> >  {
> > -       struct vectors * vecs;
> > -       int r = 1;
> > -
> > -       vecs = (struct vectors *)trigger_data;
> > -
> > -       r = parse_cmd(c);
> > -
> > -       if (r == 0 && c->cmdvec && VECTOR_SIZE(c->cmdvec) > 0) {
> > -               struct key *kw = VECTOR_SLOT(c->cmdvec, 0);
> > -
> > -               if (!c->is_root && kw->code != LIST)
> > -                       r = EPERM;
> > -       }
> > -
> > -       if (r == 0 && c->handler)
> > -               r = execute_handler(c, vecs, uxsock_timeout /
> > 1000);
> > -
> > -       if (c->cmdvec) {
> > -               free_keys(c->cmdvec);
> > -               c->cmdvec = NULL;
> > -       }
> > -
> > -       if (r > 0) {
> > -               switch(r) {
> > -               case ETIMEDOUT:
> > -                       append_strbuf_str(&c->reply, "timeout\n");
> > -                       break;
> > -               case EPERM:
> > -                       append_strbuf_str(&c->reply,
> > -                                         "permission deny: need to
> > be root\n");
> > -                       break;
> > -               default:
> > -                       append_strbuf_str(&c->reply, "fail\n");
> > -                       break;
> > -               }
> > -       }
> > -       else if (!r && get_strbuf_len(&c->reply) == 0) {
> > +       switch(r) {
> > +       case -EINVAL:
> > +       case -ESRCH:
> > +       case -ENOMEM:
> 
> get_cmdvec() returns positive errors and do_genhelp() expects
> positive
> errors, but this expects negative errors.

parse_cmd() already negates the return value of get_cmdvec().
But you're right wrt do_genhelp(). I'll fix it.

(FTR, when I worked on the patch series, I considered fixing up the
return codes of all cli_handler functions, but I gave up for now, it
was too much work for no real benefit).

Regards
Martin


--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel
diff mbox series

Patch

diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c
index ff9604f..553274b 100644
--- a/multipathd/uxlsnr.c
+++ b/multipathd/uxlsnr.c
@@ -299,22 +299,13 @@  static int parse_cmd(struct client *c)
 
 	r = get_cmdvec(c->cmd, &c->cmdvec);
 
-	if (r) {
-		genhelp_handler(c->cmd, r, &c->reply);
-		if (get_strbuf_len(&c->reply) == 0)
-			return EINVAL;
-		return 0;
-	}
+	if (r)
+		return -r;
 
 	c->handler = find_handler_for_cmdvec(c->cmdvec);
 
-	if (!c->handler || !c->handler->fn) {
-		genhelp_handler(c->cmd, EINVAL, &c->reply);
-		if (get_strbuf_len(&c->reply) == 0)
-			r = EINVAL;
-		else
-			r = 0;
-	}
+	if (!c->handler || !c->handler->fn)
+		return -EINVAL;
 
 	return r;
 }
@@ -325,7 +316,7 @@  static int execute_handler(struct client *c, struct vectors *vecs, int timeout)
 	struct timespec tmo;
 
 	if (!c->handler)
-		return EINVAL;
+		return -EINVAL;
 
 	if (clock_gettime(CLOCK_REALTIME, &tmo) == 0) {
 		tmo.tv_sec += timeout;
@@ -355,50 +346,29 @@  static int execute_handler(struct client *c, struct vectors *vecs, int timeout)
 	return r;
 }
 
-static int uxsock_trigger(struct client *c, void *trigger_data)
+void default_reply(struct client *c, int r)
 {
-	struct vectors * vecs;
-	int r = 1;
-
-	vecs = (struct vectors *)trigger_data;
-
-	r = parse_cmd(c);
-
-	if (r == 0 && c->cmdvec && VECTOR_SIZE(c->cmdvec) > 0) {
-		struct key *kw = VECTOR_SLOT(c->cmdvec, 0);
-
-		if (!c->is_root && kw->code != LIST)
-			r = EPERM;
-	}
-
-	if (r == 0 && c->handler)
-		r = execute_handler(c, vecs, uxsock_timeout / 1000);
-
-	if (c->cmdvec) {
-		free_keys(c->cmdvec);
-		c->cmdvec = NULL;
-	}
-
-	if (r > 0) {
-		switch(r) {
-		case ETIMEDOUT:
-			append_strbuf_str(&c->reply, "timeout\n");
-			break;
-		case EPERM:
-			append_strbuf_str(&c->reply,
-					  "permission deny: need to be root\n");
-			break;
-		default:
-			append_strbuf_str(&c->reply, "fail\n");
-			break;
-		}
-	}
-	else if (!r && get_strbuf_len(&c->reply) == 0) {
+	switch(r) {
+	case -EINVAL:
+	case -ESRCH:
+	case -ENOMEM:
+		/* return codes from get_cmdvec() */
+		genhelp_handler(c->cmd, r, &c->reply);
+		break;
+	case -EPERM:
+		append_strbuf_str(&c->reply,
+				  "permission deny: need to be root\n");
+		break;
+	case -ETIMEDOUT:
+		append_strbuf_str(&c->reply, "timeout\n");
+		break;
+	case 0:
 		append_strbuf_str(&c->reply, "ok\n");
-		r = 0;
+		break;
+	default:
+		append_strbuf_str(&c->reply, "fail\n");
+		break;
 	}
-	/* else if (r < 0) leave *reply alone */
-	return r;
 }
 
 static void set_client_state(struct client *c, int state)
@@ -409,6 +379,7 @@  static void set_client_state(struct client *c, int state)
 		reset_strbuf(&c->reply);
 		memset(c->cmd, '\0', sizeof(c->cmd));
 		c->expires = ts_zero;
+		c->error = 0;
 		/* fallthrough */
 	case CLT_SEND:
 		/* reuse these fields for next data transfer */
@@ -420,10 +391,13 @@  static void set_client_state(struct client *c, int state)
 	c->state = state;
 }
 
-static void handle_client(struct client *c, void *trigger_data)
+static void handle_client(struct client *c, struct vectors *vecs)
 {
 	ssize_t n;
 
+	condlog(4, "%s: cli[%d] state=%d cmd=\"%s\" repl \"%s\"", __func__,
+		c->fd, c->state, c->cmd, get_strbuf_str(&c->reply));
+
 	switch (c->state) {
 	case CLT_RECV:
 		if (c->cmd_len == 0) {
@@ -464,15 +438,52 @@  static void handle_client(struct client *c, void *trigger_data)
 				return;
 			set_client_state(c, CLT_PARSE);
 		}
-		break;
-	default:
-		break;
-	}
+		condlog(4, "cli[%d]: Got request [%s]", c->fd, c->cmd);
+		/* fallthrough */
 
-	condlog(4, "cli[%d]: Got request [%s]", c->fd, c->cmd);
-	uxsock_trigger(c, trigger_data);
+	case CLT_PARSE:
+		condlog(4, "%s: cli[%d] state=%d cmd=\"%s\" repl \"%s\"", __func__,
+			c->fd, c->state, c->cmd,  get_strbuf_str(&c->reply));
+		c->error = parse_cmd(c);
+
+		/* Permission check */
+		if (c->error == 0 && c->cmdvec && VECTOR_SIZE(c->cmdvec) > 0) {
+			struct key *kw = VECTOR_SLOT(c->cmdvec, 0);
+
+			if (!c->is_root && kw->code != LIST) {
+				/* this will fall through to CLT_SEND */
+				c->error = -EPERM;
+				condlog(0, "%s: cli[%d]: unauthorized cmd \"%s\"",
+					__func__, c->fd, c->cmd);
+			}
+		}
+		set_client_state(c, CLT_WAIT_LOCK);
+		/* fallthrough */
+
+	case CLT_WAIT_LOCK:
+		condlog(4, "%s: cli[%d] state=%d cmd=\"%s\" repl \"%s\"", __func__,
+			c->fd, c->state, c->cmd, get_strbuf_str(&c->reply));
+		/* tbd */
+		set_client_state(c, CLT_WORK);
+		/* fallthrough */
+
+	case CLT_WORK:
+		condlog(4, "%s: cli[%d] state=%d cmd=\"%s\" repl \"%s\"", __func__,
+			c->fd, c->state, c->cmd, get_strbuf_str(&c->reply));
+		if (c->error == 0 && c->handler)
+			c->error = execute_handler(c, vecs, uxsock_timeout / 1000);
+
+		if (c->cmdvec) {
+			free_keys(c->cmdvec);
+			c->cmdvec = NULL;
+		}
+		set_client_state(c, CLT_SEND);
+		/* fallthrough */
+
+	case CLT_SEND:
+		if (get_strbuf_len(&c->reply) == 0)
+			default_reply(c, c->error);
 
-	if (get_strbuf_len(&c->reply) > 0) {
 		const char *buf = get_strbuf_str(&c->reply);
 
 		if (send_packet(c->fd, buf) != 0)
@@ -481,9 +492,13 @@  static void handle_client(struct client *c, void *trigger_data)
 			condlog(4, "cli[%d]: Reply [%zu bytes]", c->fd,
 				get_strbuf_len(&c->reply) + 1);
 		reset_strbuf(&c->reply);
-	}
 
-	set_client_state(c, CLT_RECV);
+		set_client_state(c, CLT_RECV);
+		break;
+
+	default:
+		break;
+	}
 }
 
 /*