On Fri, Nov 11, 2022 at 01:17:01PM +0100, Laszlo Ersek wrote:
> On 11/10/22 00:26, Eric Blake wrote:
> > Improve some existing tests to cover client safety valves for oversize
> > requests, and add a new test that tests libnbd behavior on server
> > errors to the same sort of requests.  The new test requires a parallel
> > patch posted to nbdkit to teach 'nbdkit eval' how to forcefully
> > disconnect the server on particarly oversize write requests, emulating
> 
> s/particarly/partially/ (I believe)

Actually, I meant 'particularly'.  Either way, though, it was indeed
an unintended typo; thanks for spotting it.

> > +  /* Disable client-side safety check */
> > +  strict = nbd_get_strict_mode (nbd) & ~LIBNBD_STRICT_PAYLOAD;
> 
> The current macros are (without the first patch applied):
> 
> #define LIBNBD_STRICT_COMMANDS                   0x01
> #define LIBNBD_STRICT_FLAGS                      0x02
> #define LIBNBD_STRICT_BOUNDS                     0x04
> #define LIBNBD_STRICT_ZERO_SIZE                  0x08
> #define LIBNBD_STRICT_ALIGN                      0x10
> #define LIBNBD_STRICT_MASK                       0x1f
> 
> The 0x prefix *permits* the compiler to consider unsigned integer types
> when picking the size (type) for the integer constant, but the prefix
> does not *force* an unsigned type. And, because these values are really
> small, they all have type "int". In turn, when we apply the bitwise
> negation operator, we flip the sign bit. Not necessarily a problem on
> the platforms we care about, but not nice.
> 
> There are two ways to remove this concern. One is the following:
> 
>   strict = nbd_get_strict_mode (nbd) & ~(sometype)LIBNBD_STRICT_PAYLOAD;
> 
> where "sometype" is an unsigned integer type having at least the
> conversion rank of "int" (so that it would not be promoted to "int"),
> *and* large enough to accommodate all uint32_t values. C99 permits
> "unsigned int" to be just 16 bits, so using "unsigned int" for
> "sometype" is only safe if we are sure that on our platforms, "unsigned
> int" will always be able to represent "uint32_t". Otherwise we need a
> larger type, such as "unsigned long" (which is guaranteed by C99 even to
> hold a uint32_t).

And as the addition mirrors similar other spots in the code base, we'd
have to edit those as well.

> 
> The other way is more generic: change the generator to output all these
> constants with a "u" suffix, such as:
> 
> #define LIBNBD_STRICT_COMMANDS                   0x01u
> #define LIBNBD_STRICT_FLAGS                      0x02u
> #define LIBNBD_STRICT_BOUNDS                     0x04u
> #define LIBNBD_STRICT_ZERO_SIZE                  0x08u
> #define LIBNBD_STRICT_ALIGN                      0x10u
> #define LIBNBD_STRICT_MASK                       0x1fu
> 
> This is better because the compiler will pick "unsigned int" for them
> (once we reach huge values, the compiler will pick "unsigned long" then
> "unsigned long long"), the bitwise negation will be applied to unsigned
> integers, and then the bitwise "and" will be between a uint32_t and some
> unsigned integer type (and at that one that's not going to be promoted
> to "int", having a rank already at least as large).

Yep, that was my first thought as well as soon as I started reading
your note about the corner-case C definedness of the operation.
Having all our flag values be generated as unsigned from the get-go
makes total sense, so I'll do that as an additional patch.

> 
> Then in case "uint32_t" fits in an "int", it's going to be promoted to
> "int", and then the bit-and is between int and unsigned int --> the int
> is converted to unsigned int, done. Otherwise, we have two unsigned
> integers, the one with the smaller rank is converted to the type of the
> other.
> 
> Long story short: probably totally harmless in practice on our platforms
> (the code happens to do what's expected due to two's complement
> representation of signed integers), but it's general hygiene to avoid
> bit manipulations on signed integers (some of those happen to be
> undefined behavior even). It's good to be aware IMO that the generator
> currently produces LIBNBD_STRICT_* values that have type "int", and not
> signed int.

Indeed - totally harmless on all platforms we care to support, at
least until some new clang sanitizer starts griping at us, so we might
as well improve our definedness.

There's a very slight chance that someone could complain that flipping
our definition from signed int to unsigned int constants is an API
change, but I don't see anyone writing code that intentionally tries
to care about the signedness of the bits.  It is NOT an ABI change, at
least on the platforms that we compile on.

> 
> Feel free to ignore this comment if you think it's needlessly cautious.
> (I think it'd be best to keep the patch as-is, and then modify the
> generator in a separate patch.)

Absolutely a separate patch; already posted.

> 
> ...We could also consider the C99 standard macro UINT32_C(...):
> 
> #define LIBNBD_STRICT_COMMANDS                   UINT32_C(0x01)
> #define LIBNBD_STRICT_FLAGS                      UINT32_C(0x02)
> #define LIBNBD_STRICT_BOUNDS                     UINT32_C(0x04)
> #define LIBNBD_STRICT_ZERO_SIZE                  UINT32_C(0x08)
> #define LIBNBD_STRICT_ALIGN                      UINT32_C(0x10)
> #define LIBNBD_STRICT_MASK                       UINT32_C(0x1f)
> 
> which creates values of uint_least32_t -- but that type can (in theory)
> still be promoted to "int", if "int" is large enough. So I think the "u"
> suffix is best.

My recollection (without digging out the C99, C11, or C2x standard at
the moment) is that C allows:

char: at least 8 bits (a 9-bit char is viable on some hardware, as
  well as 32-bit char on others)
short: at least 16 bits, but at least sizeof(char)
int: at least 16 bits, but at least sizeof(short) (16 and 32 are the
  most common, but I think I've seen documentation for various
  hardware with 18- or 24-bit ints)
long: at least 32 bits, but at least sizeof(int)
long long: at least 64 bits, optional
signed values have several possible representations

POSIX (and probably C2x) further constrains things:

char: exactly 8 bits
short: exactly 16 bits
int: either 16 or 32 bits
long: either 32 or 64 bits
long long: 64 bits, mandatory
signed values use twos-complement representation

So I'm not sure whether it is even possible for a theoretical platform
where int is larger than uint_least32_t, for the latter to promote to
int.  I _do_ know, however, that UINT8_C(n) must promote to signed int
(and that for a while, various platforms had bugging libc headers that
produced unsigned constants, which had to be worked around in gnulib),
so I am not a particular fan of using the UINTx_C() macros when not
absolutely necessary.

> 
> Looks good to me otherwise:
> 
> Reviewed-by: Laszlo Ersek <ler...@redhat.com>

Series and prerequisite patches pushed as 61e88a65..122322cd

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

_______________________________________________
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs

Reply via email to