glob: Add blocks support

This change introduces the `glob_b` function which takes a block instead
of a function pointer.

Relnotes:	yes
Sponsored by:   Klara, Inc.
Inspired by:  https://github.com/apple-oss-distributions/Libc
Differential Revision:	https://reviews.freebsd.org/D50485
This commit is contained in:
Bojan Novković 2025-05-23 15:26:04 +02:00
parent 61d77e6c00
commit 1e0743f54d
5 changed files with 128 additions and 26 deletions

View file

@ -50,8 +50,15 @@ typedef struct {
size_t gl_offs; /* Reserved at beginning of gl_pathv. */
int gl_flags; /* Copy of flags parameter to glob. */
char **gl_pathv; /* List of paths matching pattern. */
/* Copy of errfunc parameter to glob. */
int (*gl_errfunc)(const char *, int);
/* Copy of error callback parameter to glob. */
union {
int (*gl_errfunc)(const char *, int);
#ifdef __BLOCKS__
int (^gl_errblk)(const char *, int);
#else
void *gl_errblk;
#endif
};
/*
* Alternate filesystem access methods for glob; replacement
@ -90,6 +97,7 @@ typedef struct {
#define GLOB_QUOTE 0x0400 /* Quote special chars with \. */
#define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */
#define GLOB_LIMIT 0x1000 /* limit number of returned paths */
#define _GLOB_ERR_BLOCK 0x08000000 /* (internal) error callback is a block */
/* source compatibility, these are the old names */
#define GLOB_MAXPATH GLOB_LIMIT
@ -99,6 +107,10 @@ typedef struct {
__BEGIN_DECLS
int glob(const char * __restrict, int,
int (*)(const char *, int), glob_t * __restrict);
#ifdef __BLOCKS__
int glob_b(const char * __restrict, int,
int (^)(const char *, int), glob_t * __restrict);
#endif
void globfree(glob_t *);
__END_DECLS

View file

@ -172,6 +172,7 @@ SRCS+= \
.if ${COMPILER_FEATURES:Mblocks}
CFLAGS.fts.c= -fblocks
CFLAGS.glob.c= -fblocks
.endif
CFLAGS.arc4random.c= -I${SRCTOP}/sys -I${SRCTOP}/sys/crypto/chacha20

View file

@ -459,6 +459,7 @@ FBSD_1.8 {
aio_write2;
execvpe;
fts_open_b;
glob_b;
psiginfo;
rtld_get_var;
rtld_set_var;

View file

@ -27,11 +27,12 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd December 20, 2011
.Dd June 02, 2025
.Dt GLOB 3
.Os
.Sh NAME
.Nm glob ,
.Nm glob_b,
.Nm globfree
.Nd generate pathnames matching a pattern
.Sh LIBRARY
@ -39,7 +40,9 @@
.Sh SYNOPSIS
.In glob.h
.Ft int
.Fn glob "const char * restrict pattern" "int flags" "int (*errfunc)(const char *, int)" "glob_t * restrict pglob"
.Fn glob "const char * restrict pattern" "int flags" "int (*errfunc)(const char *epath, int errno)" "glob_t * restrict pglob"
.Ft int
.Fn glob_b "const char * restrict pattern" "int flags" "int (^errblk)(const char *epath, int errno)" "glob_t * restrict pglob"
.Ft void
.Fn globfree "glob_t *pglob"
.Sh DESCRIPTION
@ -272,10 +275,24 @@ is
.Pf non- Dv NULL ,
.Fn glob
calls
.Fa \*(lp*errfunc\*(rp Ns ( Fa path , errno ) ,
.Fa \*(lp*errfunc\*(rp Ns ( Fa path , errno ) .
This may be unintuitive: a pattern like
.Ql */Makefile
will try to
.Xr stat 2
.Ql foo/Makefile
even if
.Ql foo
is not a directory, resulting in a
call to
.Fa errfunc .
The error routine can suppress this action by testing for
.Er ENOENT
and
.Er ENOTDIR ;
however, the
.Dv GLOB_ERR
flag will cause an immediate
flag will still cause an immediate
return when this happens.
.Pp
If
@ -307,16 +324,27 @@ or
returns zero, the error is ignored.
.Pp
The
.Fn glob_b
function is like
.Fn glob
except that the error callback is a block pointer instead of a function
pointer.
.Pp
The
.Fn globfree
function frees any space associated with
.Fa pglob
from a previous call(s) to
.Fn glob .
.Fn glob
or
.Fn glob_b .
.Sh RETURN VALUES
On successful completion,
.Fn glob
returns zero.
In addition the fields of
and
.Fn glob_b
return zero.
In addition, the fields of
.Fa pglob
contain the values described below:
.Bl -tag -width GLOB_NOCHECK
@ -324,12 +352,16 @@ contain the values described below:
contains the total number of matched pathnames so far.
This includes other matches from previous invocations of
.Fn glob
or
.Fn glob_b .
if
.Dv GLOB_APPEND
was specified.
.It Fa gl_matchc
contains the number of matched pathnames in the current invocation of
.Fn glob .
.Fn glob
or
.Fn glob_b .
.It Fa gl_flags
contains a copy of the
.Fa flags
@ -352,6 +384,8 @@ are undefined.
.Pp
If
.Fn glob
or
.Fn glob_b
terminates due to an error, it sets errno and returns one of the
following non-zero constants, which are defined in the include
file
@ -397,6 +431,14 @@ g.gl_pathv[0] = "ls";
g.gl_pathv[1] = "-l";
execvp("ls", g.gl_pathv);
.Ed
.Sh CAVEATS
The
.Fn glob
and
.Fn glob_b
functions
will not match filenames that begin with a period
unless this is specifically requested (e.g., by ".*").
.Sh SEE ALSO
.Xr sh 1 ,
.Xr fnmatch 3 ,
@ -435,6 +477,10 @@ and
.Fn globfree
functions first appeared in
.Bx 4.4 .
The
.Fn glob_b
function first appeared in
.Fx 15.0 .
.Sh BUGS
Patterns longer than
.Dv MAXPATHLEN
@ -442,7 +488,9 @@ may cause unchecked errors.
.Pp
The
.Fn glob
argument
and
.Fn glob_b
functions
may fail and set errno for any of the errors specified for the
library routines
.Xr stat 2 ,

View file

@ -88,8 +88,11 @@
#include <unistd.h>
#include <wchar.h>
#include "block_abi.h"
#include "collate.h"
typedef DECLARE_BLOCK(int, glob_b_block, const char*, int);
/*
* glob(3) expansion limits. Stop the expansion if any of these limits
* is reached. This caps the runtime in the face of DoS attacks. See
@ -179,9 +182,8 @@ static int err_aborted(glob_t *, int, char *);
static void qprintf(const char *, Char *);
#endif
int
glob(const char * __restrict pattern, int flags,
int (*errfunc)(const char *, int), glob_t * __restrict pglob)
static int
__glob(const char *pattern, glob_t *pglob)
{
struct glob_limit limit = { 0, 0, 0, 0, 0 };
const char *patnext;
@ -192,25 +194,23 @@ glob(const char * __restrict pattern, int flags,
int too_long;
patnext = pattern;
if (!(flags & GLOB_APPEND)) {
if (!(pglob->gl_flags & GLOB_APPEND)) {
pglob->gl_pathc = 0;
pglob->gl_pathv = NULL;
if (!(flags & GLOB_DOOFFS))
if (!(pglob->gl_flags & GLOB_DOOFFS))
pglob->gl_offs = 0;
}
if (flags & GLOB_LIMIT) {
if (pglob->gl_flags & GLOB_LIMIT) {
limit.l_path_lim = pglob->gl_matchc;
if (limit.l_path_lim == 0)
limit.l_path_lim = GLOB_LIMIT_PATH;
}
pglob->gl_flags = flags & ~GLOB_MAGCHAR;
pglob->gl_errfunc = errfunc;
pglob->gl_matchc = 0;
bufnext = patbuf;
bufend = bufnext + MAXPATHLEN - 1;
too_long = 1;
if (flags & GLOB_NOESCAPE) {
if (pglob->gl_flags & GLOB_NOESCAPE) {
memset(&mbs, 0, sizeof(mbs));
while (bufnext <= bufend) {
clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);
@ -250,15 +250,45 @@ glob(const char * __restrict pattern, int flags,
return (err_nomatch(pglob, &limit, pattern));
*bufnext = EOS;
if (flags & GLOB_BRACE)
if (pglob->gl_flags & GLOB_BRACE)
return (globexp0(patbuf, pglob, &limit, pattern));
else
return (glob0(patbuf, pglob, &limit, pattern));
}
int
glob(const char * __restrict pattern, int flags,
int (*errfunc)(const char *, int), glob_t * __restrict pglob)
{
int rv;
pglob->gl_flags = flags & ~(GLOB_MAGCHAR | _GLOB_ERR_BLOCK);
pglob->gl_errfunc = errfunc;
rv = __glob(pattern, pglob);
pglob->gl_errfunc = NULL;
return (rv);
}
int
glob_b(const char * __restrict pattern, int flags,
glob_b_block block, glob_t * __restrict pglob)
{
int rv;
pglob->gl_flags = flags & ~GLOB_MAGCHAR;
pglob->gl_flags |= _GLOB_ERR_BLOCK;
pglob->gl_errblk = block;
rv = __glob(pattern, pglob);
pglob->gl_errblk = NULL;
return (rv);
}
static int
globexp0(const Char *pattern, glob_t *pglob, struct glob_limit *limit,
const char *origpat) {
const char *origpat)
{
int rv;
size_t oldpathc;
@ -724,7 +754,7 @@ glob3(Char *pathbuf, Char *pathend, Char *pathend_last,
return (GLOB_NOSPACE);
}
*pathend = EOS;
if (pglob->gl_errfunc != NULL &&
if ((pglob->gl_errfunc != NULL || pglob->gl_errblk != NULL) &&
g_Ctoc(pathbuf, buf, sizeof(buf))) {
errno = E2BIG;
return (GLOB_NOSPACE);
@ -1085,10 +1115,20 @@ err_nomatch(glob_t *pglob, struct glob_limit *limit, const char *origpat) {
}
static int
err_aborted(glob_t *pglob, int err, char *buf) {
if ((pglob->gl_errfunc != NULL && pglob->gl_errfunc(buf, err)) ||
(pglob->gl_flags & GLOB_ERR))
err_aborted(glob_t *pglob, int err, char *buf)
{
int rv = 0;
if ((pglob->gl_flags & _GLOB_ERR_BLOCK) != 0) {
if (pglob->gl_errblk != NULL)
rv = CALL_BLOCK(pglob->gl_errblk, buf, errno);
} else if (pglob->gl_errfunc != NULL) {
rv = pglob->gl_errfunc(buf, errno);
}
/* GLOB_ERR is allowed to override the error callback function. */
if (rv != 0 || pglob->gl_flags & GLOB_ERR) {
return (GLOB_ABORTED);
}
return (0);
}