Hello,

it has often been mentioned that the mdoc(7) .Lb macro - which was
not part of the original language as designed by Cynthia Livingston
for USENIX and the Computer Systems Research Group (CSRG) at the
University of California in Berkeley, but introduced about a decade
later - was significantly misdesigned in two respects.

 1. The inventors of .Lb introduxed an additional section,
    LIBRARY, to be placed between NAME and SYNOPSIS.
    That is bad design for two reasons: first, documentation ought
    to get to the meat of the matter as directly as possible,
    but this additional section adds three lines of output
    before getting to the crucial SYNOPSIS section.
    On top of being wordy, the information is not even specific
    to the manual page itself but usually shared with several other
    pages, which is particularly bad in such a prominent place.
    Finally, having a dedicated section that essentially only
    contains a single word of information is overblown.

 2. Arguably, the fact that the macro expects to have access to
    an exhaustive list of libraries, associating library names
    with library descriptions, is even worse misdesign.
    These lists are almost always out of date, inconsistent
    between formatters and operating systems, sometimes even
    conflicting.  These lists have proven unmaintainable in
    practice.
    A need for a formal one-line library description simply doesn't
    exist.  Compiling and linking against a library requires a library
    file name, but not a description.  *If* a high-level overwiew page
    of a library is desired (which some libraries don't even need),
    a dedicated page like https://man.openbsd.org/crypto.3 can be
    provided, but there is no need to bloat every page by copying
    a library description line all over the place.

The reason why .Lb got traction in FreeBSD and NetBSD in spite of
these gross design issues is that it addresses a genuine concern:
for functions that are not part of the C library, programmers need
to know which library to link to, and the .Lb macro serves that
purpose, however poorly.  Since OpenBSD deliberately minimizes the
number of system libraries, whereas FreeBSD has added large numbers
of system libraries over the years, the need for such information
has been much less pronounced in OpenBSD than in FreeBSD, resulting
in the fact that FreeBSD (and NetBSD) use the .Lb macro but OpenBSD
does not.


In May 2025, Ted Unangst <tedu@> proposed a way to
 1. solve the actual task, saying which library to link
 2. display it to the user in a minimal way
 3. cause minimal effort for authors
 4. be reasonably backward-compatible with .Lb
 5. avoid all the defects of .Lb

Normal usage will go like this:

  .Sh SYNOPSIS
  .Lb libutil
  .In util.h
  .Ft int
  .Fn scan_scaled "char *number_w_scale" "long long *result"

displaying as:

  SYNOPSIS
     /* -lutil */
     #include <util.h>

     int
     scan_scaled(char *number_w_scale, long long *result);

The meaning of "-l" is obvious to any C programmer, so additional
words (like /* link with -lutil */) would be pointless verbosity,
and using C comment syntax blends in with the overall syntax
of the SYNOPSIS section.

The input syntax ".Lb libutil" was chosen for three reasons:

  1. That's the name of the library in the file system, and also
     what the library is often called in conversation.  It's
     common to hear programmers talk about "libm" or "libssl".
  2. Consequently, it's also what people are likely to search
     for with commands like "man -k Lb=libutil".
  3. It is compatible with current syntax established in
     FreeBSD, NetBSD, and illumos.

In complicated cases, where linking one library usually requires
linking one or more additional libraries, too, it will be the
author's choice to either only list the library containing
the functions in question (just as above), or to list all
libraries that should typically be linked, for example

  .Sh SYNOPSIS
  .Lb libtls libssl libcrypto
  .In tls.h
  .Ft int
  .Fn tls_init void

displaying as:

  SYNOPSIS
     /* -ltls -lssl -lcrypto */
     #include <tls.h>

     int
     tls_init(void);

Allowing multiple .Lb arguments extends the currently valid syntax,
but the result with an old formatter is merely slightly ugly and
not unintelligble, so backward compatibility is not too bad:

  SYNOPSIS
     library "libtls" libssl libcrypto
     #include <tls.h>

     int
     tls_init(void);

Since formatting will *not* change for .Lb macros outside the
SYNOPSIS section, compatibility in the other direction - old manual
pages with a new formatter - is excellent.

I checked that neither FreeBSD nor NetBSD contain any .Lb macros
in the SYNOPSIS section, so there is no problem whatsoever for them.
In illumos, there is a very small number of manual pages that
already have an .Lb macro in the SYNOPSIS section.  Presumably
they had a similar idea as tedu@ some years ago, so it seems
likely they will profit rather than suffer from the change.
Either way, it will be trivial for them to adapt.

There is consensus in OpenBSD that we want to try this direction.
Before applying it to our about 950 library manual pages,
i wanted to ask for additional feedback while there is still
time for tweaks.

If this turns out to work well in OpenBSD, FreeBSD and NetBSD
will have the *option* to follow, but there will be no pressure
for them to follow because their current scheme will just continue
to work.

I'm appending the mandoc(1) diff mainly to show how easy this
is to implement.  The implementation in groff_mdoc(7) may
turn out to be minimally more tricky, but i don't expect
needing rocket science there, either.

Yours,
  Ingo


Index: mdoc_validate.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/mdoc_validate.c,v
diff -u -p -r1.307 mdoc_validate.c
--- mdoc_validate.c     20 Sep 2024 02:00:46 -0000      1.307
+++ mdoc_validate.c     28 May 2025 13:09:57 -0000
@@ -989,13 +989,35 @@ post_ex(POST_ARGS)
 static void
 post_lb(POST_ARGS)
 {
-       struct roff_node        *n;
+       struct roff_node        *n, *nch;
+       char                    *cp;
 
        post_delim_nb(mdoc);
 
        n = mdoc->last;
-       assert(n->child->type == ROFFT_TEXT);
+       nch = n->child;
+       assert(nch->type == ROFFT_TEXT);
        mdoc->next = ROFF_NEXT_CHILD;
+
+       if (n->sec == SEC_SYNOPSIS) {
+               roff_word_alloc(mdoc, n->line, n->pos, "/*");
+               mdoc->last->flags = NODE_NOSRC;
+               while (nch != NULL) {
+                       roff_word_alloc(mdoc, n->line, n->pos, "-l");
+                       mdoc->last->flags = NODE_DELIMO | NODE_NOSRC;
+                       mdoc->last = nch;
+                       assert(nch->type == ROFFT_TEXT);
+                       cp = nch->string;
+                       if (strncmp(cp, "lib", 3) == 0)
+                               memmove(cp, cp + 3, strlen(cp) - 3 + 1);
+                       nch = nch->next;
+               }
+               roff_word_alloc(mdoc, n->line, n->pos, "*/");
+               mdoc->last->flags = NODE_NOSRC;
+               mdoc->last = n;
+               return;
+       }
+
        roff_word_alloc(mdoc, n->line, n->pos, "library");
        mdoc->last->flags = NODE_NOSRC;
        roff_word_alloc(mdoc, n->line, n->pos, "\\(lq");
Index: mandocdb.c
===================================================================
RCS file: /cvs/src/usr.bin/mandoc/mandocdb.c,v
diff -u -p -r1.221 mandocdb.c
--- mandocdb.c  14 May 2024 21:12:44 -0000      1.221
+++ mandocdb.c  28 May 2025 13:09:57 -0000
@@ -136,6 +136,8 @@ static      int      parse_mdoc_Fn(struct mpage *
                        const struct roff_node *);
 static int      parse_mdoc_Fo(struct mpage *, const struct roff_meta *,
                        const struct roff_node *);
+static int      parse_mdoc_Lb(struct mpage *, const struct roff_meta *,
+                       const struct roff_node *);
 static int      parse_mdoc_Nd(struct mpage *, const struct roff_meta *,
                        const struct roff_node *);
 static int      parse_mdoc_Nm(struct mpage *, const struct roff_meta *,
@@ -281,7 +283,7 @@ static      const struct mdoc_handler mdoc_ha
        { NULL, 0, 0 },  /* Hf */
        { NULL, 0, 0 },  /* Fr */
        { NULL, 0, 0 },  /* Ud */
-       { NULL, TYPE_Lb, NODE_NOSRC },  /* Lb */
+       { parse_mdoc_Lb, 0, 0 },  /* Lb */
        { NULL, 0, 0 },  /* Lp */
        { NULL, TYPE_Lk, 0 },  /* Lk */
        { NULL, TYPE_Mt, NODE_NOSRC },  /* Mt */
@@ -1706,6 +1708,25 @@ parse_mdoc_Fo(struct mpage *mpage, const
        if (n->child != NULL)
                parse_mdoc_fname(mpage, n->child);
 
+       return 0;
+}
+
+static int
+parse_mdoc_Lb(struct mpage *mpage, const struct roff_meta *meta,
+       const struct roff_node *n)
+{
+       char *cp;
+
+       for (n = n->child; n != NULL; n = n->next) {
+               if (n->flags & NODE_NOSRC)
+                       continue;
+               cp = n->string;
+               if (n->sec == SEC_SYNOPSIS)
+                       mandoc_asprintf(&cp, "lib%s", cp);
+               putkey(mpage, cp, TYPE_Lb);
+               if (n->sec == SEC_SYNOPSIS)
+                       free(cp);
+       }
        return 0;
 }
 

Reply via email to