On Thu, Sep 07, 2023 at 10:45:58AM -0700, Lokesh Jindal wrote:
> Resending the patch with the right email subject. Previos email
> discussion - here
> <https://www.mail-archive.com/haproxy@formilux.org/msg43973.html>.

I saw your previous mail, just didn't have time yet to review it.

> PFA the patch file.

Thanks!

> I have two questions in general:
> 1. Can you explain what this method does and why is it needed? (I used it
> in my patch following the pattern in the converter "sub")
> *smp_set_owner(&smp_arg0, smp->px, smp->sess, smp->strm, smp->opt);**

It just makes sure that the newly created sample properly references
the context from which it was created. I remember that *some* functions
retrieve some elements there, maybe ACLs or Lua actions that need to
yield, etc.

> 2. In my patch, *sample_conv_bytes* returns 0 in case of an invalid arg
> e.g. negative offset. Can you explain when a converter should return 0 V 1?
> I am not sure how that impacts the haproxy behavior.

Normally when evaluating an expression, 0 is returned to indicate
"nothing for now" but this doesn't preclude that it will not be possible
to have more info later (depending on extra flags added such as
SMP_F_MAY_CHANGE). 1 normally indicates that we have a result and that
the sample is correctly filled. If a zero is returned without any flag,
it will usually abort the evaluation of the expression (e.g. it cannot
be converted thus processed). ACL evaluation will not go further with
that sample for example. If 1 is returned but the sample type is not
filled, implicitly the caller will see that SMP_T_ANY is returned and
not compatible with the next expected type so it will stop there as well.
Thus I think that returning 0 or 1 in case of failure to process args will
achieve the same result but for slightly different reasons. 0 is probably
cleaner though.

> +             if (smp_arg1.data.u.sint < 0 || (smp_arg1.data.u.sint > 
> (smp->data.u.str.data - start_idx))) {
> +                     // empty output if the arg2 value is negative or bigger 
> than the remaining length
> +                     smp->data.u.str.data = 0;
> +                     return 0;
> +             }

Here I'm wondering what we should do if the user asks for more bytes than
available. I think we have 3 options:
  - return 0 so that the sample's flags are consulted by the caller (e.g.
    SMP_F_MAY_CHANGE will allow the caller to come back later trying to
    get more bytes, or fail if not enough)

  - truncate to what is available

  - maybe a combination of both, i.e. return 0 as long as the caller does
    not pass SMP_OPT_FINAL, or return a truncated response.

Truncation can be useful but I could understand that when extracting bytes
one would want this exact number of bytes to continue the processing. I
just wanted to mention this to make sure this is what you had in mind as
well.

> +             length = smp_arg1.data.u.sint;
> +     } else {
> +             length = smp->data.u.str.data - start_idx;
> +     }
>  
> -     if ((arg_p[1].type == ARGT_SINT) && (arg_p[1].data.sint < 
> smp->data.u.str.data))
> -             smp->data.u.str.data = arg_p[1].data.sint;

It seems to me that previously we would truncate if less bytes were
available, so maybe we'll need to preserve this or do a mix of the
two as I mentioned above.

> +     // update the output using the start_idx and length
> +     smp->data.u.str.area += start_idx;
> +     smp->data.u.str.data = length;
>  
>       return 1;
>  }
> @@ -3120,7 +3144,7 @@ static int check_operator(struct arg *args, struct 
> sample_conv *conv,
>       long long int i;
>  
>       /* Try to decode a variable. */
> -     if (vars_check_arg(&args[0], NULL))
> +     if (vars_check_arg(&args[0], err))
>               return 1;

I can't say if this change was intentional or not, nor if the NULL had a
particular reason or not, but at least this is unrelated to your patch
and would need to be in its own patch, so that in case of regression we
can decide whether to fix or revert it. And if it really fixes an issue
maybe we'll want to backport it.

>       /* Try to convert an integer */
> @@ -4905,7 +4929,34 @@ static int smp_fetch_conn_timers(const struct arg 
> *args, struct sample *smp, con
>       return 0;
>  }
>  
> +static int sample_conv_bytes_check(struct arg *args, struct sample_conv 
> *conv,
> +                          const char *file, int line, char **err)
> +{
> +     // arg0 is not optional, must be >= 0
> +     if (!check_operator(&args[0], conv, file, line, err)) {
> +             return 0;
> +     }
> +    if (args[0].type != ARGT_VAR) {
> +        if (args[0].type != ARGT_SINT || args[0].data.sint < 0) {
> +            memprintf(err, "expects a non-negative integer");
> +            return 0;
> +        }
> +    }

You're having indentation issues in the "if" block above, which only
uses 4-spaces instead of a tab.

> +     // arg1 is optional, must be > 0
> +     if (args[1].type != ARGT_STOP) {
> +             if (!check_operator(&args[1], conv, file, line, err)) {
> +             return 0;
> +             }

And here the "return" misses an indent level.

> +             if (args[1].type != ARGT_VAR) {
> +                     if (args[1].type != ARGT_SINT || args[1].data.sint <= 
> 0) {
> +                             memprintf(err, "expects a positive integer");
> +                             return 0;
> +                     }
> +             }
> +     }
>  
> +     return 1;
> +}
(...)

OK overall that looks pretty good. Just one trivial patch to split, a
need to decide what to do on too small inputs, and trivial cleanups. Ah
also, please make sure to wrap your text at 80-cols in the doc (having
the examples slightly exceed it is not critical though as some users
prefer to easily copy-paste them).

I predict the next one will be mergeable as-is :-)

Thanks!
Willy

Reply via email to