Skip to content

Conversation

@alejandro-colomar
Copy link
Collaborator

@alejandro-colomar alejandro-colomar commented Aug 16, 2025

These APIs are like streq() and strprefix(), but for nonstrings (thus, the usual strn*() name).

The first parameter is a nonstring, and the second is a string.


Revisions:

v2
  • Drop STRNPFX() from this patch set. I'll keep that for later.
$ git range-diff master gh/strneq strneq 
1:  cc5fcce1 = 1:  cc5fcce1 lib/, src/: Use strncmp(3) instead of explicit byte comparisons
2:  c6f4546b = 2:  c6f4546b lib/string/strcmp/: strneq(), STRNEQ(): Add APIs
3:  c7bc9f1d = 3:  c7bc9f1d lib/, src/: Use STRNEQ() instead of their pattern
4:  b1f734cf < -:  -------- lib/string/strcmp/: strnpfx(), STRNPFX(): Add APIs
5:  fbdade07 < -:  -------- src/logoutd.c: Use STRNPFX() instead of its pattern
v2b
  • Rebase
$ git rd
1:  cc5fcce1 = 1:  f41a6069 lib/, src/: Use strncmp(3) instead of explicit byte comparisons
2:  c6f4546b = 2:  23b111ae lib/string/strcmp/: strneq(), STRNEQ(): Add APIs
3:  c7bc9f1d = 3:  fc42cc1b lib/, src/: Use STRNEQ() instead of their pattern
v3c
  • Update lib/string/README.
$ git range-diff shadow/master gh/strneq strneq 
1:  f41a6069 = 1:  f41a6069 lib/, src/: Use strncmp(3) instead of explicit byte comparisons
2:  23b111ae ! 2:  30c4e70d lib/string/strcmp/: strneq(), STRNEQ(): Add APIs
    @@ lib/Makefile.am: libshadow_la_SOURCES = \
        string/strcmp/strprefix.h \
        string/strcpy/stpecpy.c \
     
    + ## lib/string/README ##
    +@@ lib/string/README: Don't use some libc functions without Really Good Reasons:
    +   The return value of strcmp(3) is confusing.
    +   strcmp(3) would be legitimate for sorting strings.
    + 
    ++    strncmp(3)
    ++  Use STRNEQ(), or strpfx(), or else, depending on what you want.
    ++  The return value of strncmp(3) is confusing,
    ++  and it is unclear the purpose of its use when you read it.
    ++
    +     strcasecmp(3)
    +   Use strcaseeq() instead.
    + 
    +@@ lib/string/README: strcmp/ - String comparison
    +   Like strsfx(), but ignore upper-/lower-case.
    + 
    +   n/
    +-    strneq()  // Unimplemented
    ++    strneq()
    +   Return true if a [[gnu::nonstring]] is equal to a string.
    +-    STRNEQ()  // Unimplemented
    ++    STRNEQ()
    +   Like strneq(), but takes an array.
    + 
    +     strnpfx()  // Unimplemented
    +
      ## lib/string/strcmp/strneq.c (new) ##
     @@
     +// SPDX-FileCopyrightText: 2025, Alejandro Colomar <alx@kernel.org>
3:  fc42cc1b = 3:  49a51b7e lib/, src/: Use STRNEQ() instead of their pattern
v4
  • Use [[gnu::nonstring]].
$ git range-diff shadow/master gh/strneq strneq 
1:  f41a6069 = 1:  f41a6069 lib/, src/: Use strncmp(3) instead of explicit byte comparisons
-:  -------- > 2:  cb760ff7 lib/attr.h: __has_c_attribute(): Define fallback
-:  -------- > 3:  44ff1464 lib/attr.h: ATTR_NONSTRING: Add attribute [[gnu::nonstring]]
2:  30c4e70d ! 4:  a302e99e lib/string/strcmp/: strneq(), STRNEQ(): Add APIs
    @@ lib/string/strcmp/strneq.h (new)
     +
     +
     +ATTR_STRING(2)
    -+inline bool strneq(const char *strn, const char *s, size_t size);
    ++inline bool strneq(ATTR_NONSTRING const char *strn, const char *s, size_t size);
     +
     +
     +// nonstring equal
3:  49a51b7e = 5:  39c64afc lib/, src/: Use STRNEQ() instead of their pattern
v4b
  • Rebase
$ git rd 
1:  f41a6069 = 1:  a9f960b9 lib/, src/: Use strncmp(3) instead of explicit byte comparisons
2:  cb760ff7 = 2:  419505c3 lib/attr.h: __has_c_attribute(): Define fallback
3:  44ff1464 = 3:  40602dd4 lib/attr.h: ATTR_NONSTRING: Add attribute [[gnu::nonstring]]
4:  a302e99e = 4:  db0d2614 lib/string/strcmp/: strneq(), STRNEQ(): Add APIs
5:  39c64afc = 5:  cb9791cc lib/, src/: Use STRNEQ() instead of their pattern
v4c
  • Rebase
$ git rd 
1:  a9f960b9 = 1:  e9b33402 lib/, src/: Use strncmp(3) instead of explicit byte comparisons
2:  419505c3 = 2:  bb0ec038 lib/attr.h: __has_c_attribute(): Define fallback
3:  40602dd4 = 3:  bc8f6f03 lib/attr.h: ATTR_NONSTRING: Add attribute [[gnu::nonstring]]
4:  db0d2614 = 4:  d34eff6d lib/string/strcmp/: strneq(), STRNEQ(): Add APIs
5:  cb9791cc = 5:  6ef63f69 lib/, src/: Use STRNEQ() instead of their pattern

@alejandro-colomar alejandro-colomar changed the title Add STRNEQ(), and use it instead of its pattern Add STRNEQ(), STRNPFX(), and use them instead of its pattern Aug 16, 2025
@alejandro-colomar alejandro-colomar force-pushed the strneq branch 4 times, most recently from 1612a74 to fbdade0 Compare August 16, 2025 22:52
@alejandro-colomar alejandro-colomar marked this pull request as ready for review August 16, 2025 23:05
@alejandro-colomar alejandro-colomar changed the title Add STRNEQ(), STRNPFX(), and use them instead of its pattern Add STRNEQ(), STRNPFX(), and use them instead of their patterns Aug 16, 2025
@alejandro-colomar alejandro-colomar changed the title Add STRNEQ(), STRNPFX(), and use them instead of their patterns Add STRNEQ(), and use it instead of its pattern Sep 30, 2025
@alejandro-colomar alejandro-colomar force-pushed the strneq branch 2 times, most recently from fc42cc1 to 49a51b7 Compare September 30, 2025 08:13
@alejandro-colomar alejandro-colomar force-pushed the strneq branch 2 times, most recently from 39c64af to cb9791c Compare October 7, 2025 09:22
lib/utmp.c Outdated

#if defined(HAVE_STRUCT_UTMPX_UT_HOST)
if ((ut != NULL) && (ut->ut_host[0] != '\0')) {
if ((ut != NULL) && strncmp(ut->ut_host, "", countof(ut->ut_host)) != 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree that this is simpler to read. But also, what's the advantage of
using strncmp here, instead of just strcmp(x, "");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you really want to not see the byte comparisons (... I like them ...) you
could add an is_empty_string() fn? That would be easier to read.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree that this is simpler to read.

I agree that is not simpler to read.

strncmp(3) is ugly because of the countof() call, and because of the !=0 comparison. However, this is later changed to use the STRNEQ() macro that has 2 arguments. So, the diff after the entire PR would be:

-if ((ut != NULL) && (ut->ut_host[0] != '\0')) {
+if ((ut != NULL) && !STRNEQ(ut->ut_host, "")) {

But also, what's the advantage of using strncmp here, instead of just strcmp(x, "");

strcmp(3) requires that its arguments are strings, but ut->ut_host is not a string; it might not be null terminated.
Thus, using strcmp(3) might result in a buffer overrun. In this specific case, since we won't compare more than one byte, it's "fine", however, it's more hygienic to not do that. Programmers could be confused and think that it could be a string by seeing it being passed to strcmp(3).

Also, I expect that in the future it would even trigger a diagnostic, once glibc starts annotating their functions with the new GCC attribute [[gnu::null_terminated_string_arg()]].

That's why I'm adding strneq(), to compare a nonstring instead of a string.

Another problem with str[n]cmp(3), which is solved with str[n]eq(), is the confusing return value.

f you really want to not see the byte comparisons (... I like them ...)

The thing about byte comparisons is that they're hard to grep(1) for. Also, it's hard to know what kind of thing you're comparing, and why. If you see strneq(), you know your comparing a nonstring. If you see streq(), you see you're comparing a string.

you
could add an is_empty_string() fn? That would be easier to read.

We'd need an is_empty_nonstring() for this case. I think comparing to "" for equality is robust enough that I don't see it very compelling to add something like str[n]empty(). Maybe we could discuss that after all of this.

@hallyn
Copy link
Member

hallyn commented Oct 18, 2025

Could we replace the !STRNEQ(ut->ut_host, "")) etc with something like !strempty(ut->ut_host)?

@alejandro-colomar
Copy link
Collaborator Author

alejandro-colomar commented Oct 18, 2025

Could we replace the !STRNEQ(ut->ut_host, "")) etc with something like !strempty(ut->ut_host)?

strempty() would replace streq(). For STRNEQ(), we'd need STRNEMPTY(). I'm open to that. However, are you sure you'd find it more readable? It's one more layer to look up, and streq() / STRNEQ() has more chances of being a widely-known API.

About the uppercase macros for arrays and your dislike of shouting names, I've been thinking recently, and found some consistent way to name them, that I like better than uppercase: an _a suffix (a for array). So, strneq_a() instead of STRNEQ(). (Or strnempty_a() instead of strnempty().)

This is simpler to read, IMO.

Signed-off-by: Alejandro Colomar <alx@kernel.org>
This allows using __has_c_attribute() in compilers that don't have it.

Signed-off-by: Alejandro Colomar <alx@kernel.org>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
@hallyn hallyn merged commit 8f0fa6d into shadow-maint:master Oct 18, 2025
11 checks passed
@alejandro-colomar alejandro-colomar deleted the strneq branch October 19, 2025 06:35
@alejandro-colomar alejandro-colomar self-assigned this Dec 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants