fts: Add blocks support.
This adds an `fts_open_b()` variant of `fts_open()` which takes a block instead of a function pointer. This was inspired by, and is intended to be compatible with, Apple's implementation; however, although our FTS and theirs share a common ancestor, they have diverged significantly. That and the fact that we still target compilers which don't support blocks means Apple's implementation was not directly reusable. This is the second use case for blocks in FreeBSD (the first being `qsort_b()`, which we use here). This suggest we might want to add a `COMPILER_FEATURE` for blocks to avoid hardcoding any further `COMPILER_TYPE` checks. MFC after: never Relnotes: yes Sponsored by: Klara, Inc. Reviewed by: kevans, theraven, imp Differential Revision: https://reviews.freebsd.org/D49877
This commit is contained in:
+22
-7
@@ -34,17 +34,27 @@
|
||||
|
||||
#include <sys/_types.h>
|
||||
|
||||
typedef struct _ftsent FTSENT;
|
||||
|
||||
typedef struct {
|
||||
struct _ftsent *fts_cur; /* current node */
|
||||
struct _ftsent *fts_child; /* linked list of children */
|
||||
struct _ftsent **fts_array; /* sort array */
|
||||
FTSENT *fts_cur; /* current node */
|
||||
FTSENT *fts_child; /* linked list of children */
|
||||
FTSENT **fts_array; /* sort array */
|
||||
__dev_t fts_dev; /* starting device # */
|
||||
char *fts_path; /* path for this descent */
|
||||
int fts_rfd; /* fd for root */
|
||||
__size_t fts_pathlen; /* sizeof(path) */
|
||||
__size_t fts_nitems; /* elements in the sort array */
|
||||
int (*fts_compar) /* compare function */
|
||||
(const struct _ftsent * const *, const struct _ftsent * const *);
|
||||
union {
|
||||
int (*fts_compar) /* compare function */
|
||||
(const FTSENT * const *, const FTSENT * const *);
|
||||
#ifdef __BLOCKS__
|
||||
int (^fts_compar_b)
|
||||
(const FTSENT * const *, const FTSENT * const *);
|
||||
#else
|
||||
void *fts_compar_b;
|
||||
#endif /* __BLOCKS__ */
|
||||
};
|
||||
|
||||
/* valid for fts_open() */
|
||||
#define FTS_COMFOLLOW 0x000001 /* follow command line symlinks */
|
||||
@@ -62,11 +72,12 @@ typedef struct {
|
||||
|
||||
/* internal use only */
|
||||
#define FTS_STOP 0x010000 /* unrecoverable error */
|
||||
#define FTS_COMPAR_B 0x020000 /* compare function is a block */
|
||||
int fts_options; /* fts_open options, global flags */
|
||||
void *fts_clientptr; /* thunk for sort function */
|
||||
} FTS;
|
||||
|
||||
typedef struct _ftsent {
|
||||
struct _ftsent {
|
||||
struct _ftsent *fts_cycle; /* cycle node */
|
||||
struct _ftsent *fts_parent; /* parent directory */
|
||||
struct _ftsent *fts_link; /* next file in directory */
|
||||
@@ -118,7 +129,7 @@ typedef struct _ftsent {
|
||||
struct stat *fts_statp; /* stat(2) information */
|
||||
char *fts_name; /* file name */
|
||||
FTS *fts_fts; /* back pointer to main FTS */
|
||||
} FTSENT;
|
||||
};
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
@@ -131,6 +142,10 @@ FTS *fts_get_stream(FTSENT *);
|
||||
#define fts_get_stream(ftsent) ((ftsent)->fts_fts)
|
||||
FTS *fts_open(char * const *, int,
|
||||
int (*)(const FTSENT * const *, const FTSENT * const *));
|
||||
#ifdef __BLOCKS__
|
||||
FTS *fts_open_b(char * const *, int,
|
||||
int (^)(const FTSENT * const *, const FTSENT * const *));
|
||||
#endif /* __BLOCKS__ */
|
||||
FTSENT *fts_read(FTS *);
|
||||
int fts_set(FTS *, FTSENT *, int);
|
||||
void fts_set_clientptr(FTS *, void *);
|
||||
|
||||
@@ -168,6 +168,10 @@ SRCS+= \
|
||||
valloc.c \
|
||||
wordexp.c
|
||||
|
||||
.if ${COMPILER_TYPE} == "clang"
|
||||
CFLAGS.fts.c= -fblocks
|
||||
.endif
|
||||
|
||||
CFLAGS.arc4random.c= -I${SRCTOP}/sys -I${SRCTOP}/sys/crypto/chacha20
|
||||
|
||||
CFLAGS.sysconf.c= -I${SRCTOP}/contrib/tzcode
|
||||
|
||||
@@ -458,6 +458,7 @@ FBSD_1.8 {
|
||||
aio_read2;
|
||||
aio_write2;
|
||||
execvpe;
|
||||
fts_open_b;
|
||||
psiginfo;
|
||||
rtld_get_var;
|
||||
rtld_set_var;
|
||||
|
||||
+45
-13
@@ -25,7 +25,7 @@
|
||||
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
.\" SUCH DAMAGE.
|
||||
.\"
|
||||
.Dd January 12, 2014
|
||||
.Dd April 17, 2025
|
||||
.Dt FTS 3
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -37,6 +37,8 @@
|
||||
.In fts.h
|
||||
.Ft FTS *
|
||||
.Fn fts_open "char * const *path_argv" "int options" "int (*compar)(const FTSENT * const *, const FTSENT * const *)"
|
||||
.Ft FTS *
|
||||
.Fn fts_open_b "char * const *path_argv" "int options" "int (^compar)(const FTSENT * const *, const FTSENT * const *)"
|
||||
.Ft FTSENT *
|
||||
.Fn fts_read "FTS *ftsp"
|
||||
.Ft FTSENT *
|
||||
@@ -59,7 +61,9 @@ functions are provided for traversing
|
||||
file hierarchies.
|
||||
A simple overview is that the
|
||||
.Fn fts_open
|
||||
function returns a
|
||||
and
|
||||
.Fn fts_open_b
|
||||
functions return a
|
||||
.Dq handle
|
||||
on a file hierarchy, which is then supplied to
|
||||
the other
|
||||
@@ -186,6 +190,8 @@ or
|
||||
.Ql ..\&
|
||||
which was not specified as a file name to
|
||||
.Fn fts_open
|
||||
or
|
||||
.Fn fts_open_b
|
||||
(see
|
||||
.Dv FTS_SEEDOT ) .
|
||||
.It Dv FTS_DP
|
||||
@@ -234,6 +240,8 @@ A path for accessing the file from the current directory.
|
||||
The path for the file relative to the root of the traversal.
|
||||
This path contains the path specified to
|
||||
.Fn fts_open
|
||||
or
|
||||
.Fn fts_open_b
|
||||
as a prefix.
|
||||
.It Fa fts_pathlen
|
||||
The length of the string referenced by
|
||||
@@ -518,6 +526,15 @@ the directory traversal order is in the order listed in
|
||||
.Fa path_argv
|
||||
for the root paths, and in the order listed in the directory for
|
||||
everything else.
|
||||
.Sh FTS_OPEN_B
|
||||
The
|
||||
.Fn fts_open_b
|
||||
function is identical to
|
||||
.Fn fts_open
|
||||
except that it takes a block pointer instead of a function pointer.
|
||||
The block is copied before
|
||||
.Fn fts_open_b
|
||||
returns, so the original can safely go out of scope or be released.
|
||||
.Sh FTS_READ
|
||||
The
|
||||
.Fn fts_read
|
||||
@@ -593,9 +610,13 @@ As a special case, if
|
||||
has not yet been called for a hierarchy,
|
||||
.Fn fts_children
|
||||
will return a pointer to the files in the logical directory specified to
|
||||
.Fn fts_open ,
|
||||
.Fn fts_open
|
||||
or
|
||||
.Fn fts_open_b ,
|
||||
i.e., the arguments specified to
|
||||
.Fn fts_open .
|
||||
.Fn fts_open
|
||||
or
|
||||
.Fn fts_open_b .
|
||||
Otherwise, if the
|
||||
.Vt FTSENT
|
||||
structure most recently returned by
|
||||
@@ -716,6 +737,8 @@ function closes a file hierarchy stream
|
||||
.Fa ftsp
|
||||
and restores the current directory to the directory from which
|
||||
.Fn fts_open
|
||||
or
|
||||
.Fn fts_open_b
|
||||
was called to open
|
||||
.Fa ftsp .
|
||||
The
|
||||
@@ -723,29 +746,38 @@ The
|
||||
function
|
||||
returns 0 on success, and \-1 if an error occurs.
|
||||
.Sh ERRORS
|
||||
The function
|
||||
The
|
||||
.Fn fts_open
|
||||
may fail and set
|
||||
and
|
||||
.Fn fts_open_b
|
||||
functions may fail and set
|
||||
.Va errno
|
||||
for any of the errors specified for the library functions
|
||||
.Xr open 2
|
||||
and
|
||||
.Xr malloc 3 .
|
||||
The
|
||||
.Fn fts_open_b
|
||||
function may also fail and set
|
||||
.Va errno
|
||||
to
|
||||
.Dv ENOSYS
|
||||
if the blocks runtime is missing.
|
||||
.Pp
|
||||
The function
|
||||
The
|
||||
.Fn fts_close
|
||||
may fail and set
|
||||
function may fail and set
|
||||
.Va errno
|
||||
for any of the errors specified for the library functions
|
||||
.Xr chdir 2
|
||||
and
|
||||
.Xr close 2 .
|
||||
.Pp
|
||||
The functions
|
||||
The
|
||||
.Fn fts_read
|
||||
and
|
||||
.Fn fts_children
|
||||
may fail and set
|
||||
functions may fail and set
|
||||
.Va errno
|
||||
for any of the errors specified for the library functions
|
||||
.Xr chdir 2 ,
|
||||
@@ -755,12 +787,12 @@ for any of the errors specified for the library functions
|
||||
and
|
||||
.Xr stat 2 .
|
||||
.Pp
|
||||
In addition,
|
||||
In addition, the
|
||||
.Fn fts_children ,
|
||||
.Fn fts_open
|
||||
.Fn fts_open ,
|
||||
and
|
||||
.Fn fts_set
|
||||
may fail and set
|
||||
functions may fail and set
|
||||
.Va errno
|
||||
as follows:
|
||||
.Bl -tag -width Er
|
||||
|
||||
+130
-42
@@ -48,6 +48,19 @@
|
||||
|
||||
#include "gen-private.h"
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
#include <Block.h>
|
||||
#else
|
||||
#include "block_abi.h"
|
||||
typedef DECLARE_BLOCK(int, fts_block,
|
||||
const FTSENT * const *, const FTSENT * const *);
|
||||
void qsort_b(void *, size_t, size_t, fts_block);
|
||||
#endif /* __BLOCKS__ */
|
||||
/* only present if linked with blocks runtime */
|
||||
void *_Block_copy(const void *) __weak_symbol;
|
||||
void _Block_release(const void *) __weak_symbol;
|
||||
extern void *_NSConcreteGlobalBlock[] __weak_symbol;
|
||||
|
||||
static FTSENT *fts_alloc(FTS *, char *, size_t);
|
||||
static FTSENT *fts_build(FTS *, int);
|
||||
static void fts_lfree(FTSENT *);
|
||||
@@ -102,35 +115,13 @@ static const char *ufslike_filesystems[] = {
|
||||
0
|
||||
};
|
||||
|
||||
FTS *
|
||||
fts_open(char * const *argv, int options,
|
||||
int (*compar)(const FTSENT * const *, const FTSENT * const *))
|
||||
static FTS *
|
||||
__fts_open(FTS *sp, char * const *argv)
|
||||
{
|
||||
struct _fts_private *priv;
|
||||
FTS *sp;
|
||||
FTSENT *p, *root;
|
||||
FTSENT *parent, *tmp;
|
||||
size_t len, nitems;
|
||||
|
||||
/* Options check. */
|
||||
if (options & ~FTS_OPTIONMASK) {
|
||||
errno = EINVAL;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* fts_open() requires at least one path */
|
||||
if (*argv == NULL) {
|
||||
errno = EINVAL;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Allocate/initialize the stream. */
|
||||
if ((priv = calloc(1, sizeof(*priv))) == NULL)
|
||||
return (NULL);
|
||||
sp = &priv->ftsp_fts;
|
||||
sp->fts_compar = compar;
|
||||
sp->fts_options = options;
|
||||
|
||||
/* Logical walks turn on NOCHDIR; symbolic links are too hard. */
|
||||
if (ISSET(FTS_LOGICAL))
|
||||
SET(FTS_NOCHDIR);
|
||||
@@ -168,7 +159,7 @@ fts_open(char * const *argv, int options,
|
||||
* If comparison routine supplied, traverse in sorted
|
||||
* order; otherwise traverse in the order specified.
|
||||
*/
|
||||
if (compar) {
|
||||
if (sp->fts_compar) {
|
||||
p->fts_link = root;
|
||||
root = p;
|
||||
} else {
|
||||
@@ -181,7 +172,7 @@ fts_open(char * const *argv, int options,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (compar && nitems > 1)
|
||||
if (sp->fts_compar && nitems > 1)
|
||||
root = fts_sort(sp, root, nitems);
|
||||
|
||||
/*
|
||||
@@ -214,6 +205,97 @@ mem1: free(sp);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
FTS *
|
||||
fts_open(char * const *argv, int options,
|
||||
int (*compar)(const FTSENT * const *, const FTSENT * const *))
|
||||
{
|
||||
struct _fts_private *priv;
|
||||
FTS *sp;
|
||||
|
||||
/* Options check. */
|
||||
if (options & ~FTS_OPTIONMASK) {
|
||||
errno = EINVAL;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* fts_open() requires at least one path */
|
||||
if (*argv == NULL) {
|
||||
errno = EINVAL;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Allocate/initialize the stream. */
|
||||
if ((priv = calloc(1, sizeof(*priv))) == NULL)
|
||||
return (NULL);
|
||||
sp = &priv->ftsp_fts;
|
||||
sp->fts_compar = compar;
|
||||
sp->fts_options = options;
|
||||
|
||||
return (__fts_open(sp, argv));
|
||||
}
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
FTS *
|
||||
fts_open_b(char * const *argv, int options,
|
||||
int (^compar)(const FTSENT * const *, const FTSENT * const *))
|
||||
#else
|
||||
FTS *
|
||||
fts_open_b(char * const *argv, int options, fts_block compar)
|
||||
#endif /* __BLOCKS__ */
|
||||
{
|
||||
struct _fts_private *priv;
|
||||
FTS *sp;
|
||||
|
||||
/* No blocks, no problems. */
|
||||
if (compar == NULL)
|
||||
return (fts_open(argv, options, NULL));
|
||||
|
||||
/* Avoid segfault if blocks runtime is missing. */
|
||||
if (_Block_copy == NULL) {
|
||||
errno = ENOSYS;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Options check. */
|
||||
if (options & ~FTS_OPTIONMASK) {
|
||||
errno = EINVAL;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* fts_open() requires at least one path */
|
||||
if (*argv == NULL) {
|
||||
errno = EINVAL;
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Allocate/initialize the stream. */
|
||||
if ((priv = calloc(1, sizeof(*priv))) == NULL)
|
||||
return (NULL);
|
||||
sp = &priv->ftsp_fts;
|
||||
#ifdef __BLOCKS__
|
||||
compar = Block_copy(compar);
|
||||
#else
|
||||
if (compar->isa != &_NSConcreteGlobalBlock)
|
||||
compar = _Block_copy(compar);
|
||||
#endif /* __BLOCKS__ */
|
||||
if (compar == NULL) {
|
||||
free(priv);
|
||||
return (NULL);
|
||||
}
|
||||
sp->fts_compar_b = compar;
|
||||
sp->fts_options = options | FTS_COMPAR_B;
|
||||
|
||||
if ((sp = __fts_open(sp, argv)) == NULL) {
|
||||
#ifdef __BLOCKS__
|
||||
Block_release(compar);
|
||||
#else
|
||||
if (compar->isa != &_NSConcreteGlobalBlock)
|
||||
_Block_release(compar);
|
||||
#endif /* __BLOCKS__ */
|
||||
}
|
||||
return (sp);
|
||||
}
|
||||
|
||||
static void
|
||||
fts_load(FTS *sp, FTSENT *p)
|
||||
{
|
||||
@@ -265,6 +347,16 @@ fts_close(FTS *sp)
|
||||
free(sp->fts_array);
|
||||
free(sp->fts_path);
|
||||
|
||||
/* Free up any block pointer. */
|
||||
if (ISSET(FTS_COMPAR_B) && sp->fts_compar_b != NULL) {
|
||||
#ifdef __BLOCKS__
|
||||
Block_release(sp->fts_compar_b);
|
||||
#else
|
||||
if (sp->fts_compar_b->isa != &_NSConcreteGlobalBlock)
|
||||
_Block_release(sp->fts_compar_b);
|
||||
#endif /* __BLOCKS__ */
|
||||
}
|
||||
|
||||
/* Return to original directory, save errno if necessary. */
|
||||
if (!ISSET(FTS_NOCHDIR)) {
|
||||
saved_errno = fchdir(sp->fts_rfd) ? errno : 0;
|
||||
@@ -979,21 +1071,6 @@ err: memset(sbp, 0, sizeof(struct stat));
|
||||
return (FTS_DEFAULT);
|
||||
}
|
||||
|
||||
/*
|
||||
* The comparison function takes pointers to pointers to FTSENT structures.
|
||||
* Qsort wants a comparison function that takes pointers to void.
|
||||
* (Both with appropriate levels of const-poisoning, of course!)
|
||||
* Use a trampoline function to deal with the difference.
|
||||
*/
|
||||
static int
|
||||
fts_compar(const void *a, const void *b)
|
||||
{
|
||||
FTS *parent;
|
||||
|
||||
parent = (*(const FTSENT * const *)a)->fts_fts;
|
||||
return (*parent->fts_compar)(a, b);
|
||||
}
|
||||
|
||||
static FTSENT *
|
||||
fts_sort(FTS *sp, FTSENT *head, size_t nitems)
|
||||
{
|
||||
@@ -1016,7 +1093,18 @@ fts_sort(FTS *sp, FTSENT *head, size_t nitems)
|
||||
}
|
||||
for (ap = sp->fts_array, p = head; p; p = p->fts_link)
|
||||
*ap++ = p;
|
||||
qsort(sp->fts_array, nitems, sizeof(FTSENT *), fts_compar);
|
||||
if (ISSET(FTS_COMPAR_B)) {
|
||||
#ifdef __BLOCKS__
|
||||
qsort_b(sp->fts_array, nitems, sizeof(FTSENT *),
|
||||
(int (^)(const void *, const void *))sp->fts_compar_b);
|
||||
#else
|
||||
qsort_b(sp->fts_array, nitems, sizeof(FTSENT *),
|
||||
sp->fts_compar_b);
|
||||
#endif /* __BLOCKS__ */
|
||||
} else {
|
||||
qsort(sp->fts_array, nitems, sizeof(FTSENT *),
|
||||
(int (*)(const void *, const void *))sp->fts_compar);
|
||||
}
|
||||
for (head = *(ap = sp->fts_array); --nitems; ++ap)
|
||||
ap[0]->fts_link = ap[1];
|
||||
ap[0]->fts_link = NULL;
|
||||
|
||||
@@ -7,6 +7,9 @@ ATF_TESTS_C+= fmtcheck2_test
|
||||
ATF_TESTS_C+= fmtmsg_test
|
||||
ATF_TESTS_C+= fnmatch2_test
|
||||
ATF_TESTS_C+= fpclassify2_test
|
||||
.if ${COMPILER_TYPE} == "clang"
|
||||
ATF_TESTS_C+= fts_blocks_test
|
||||
.endif
|
||||
ATF_TESTS_C+= ftw_test
|
||||
ATF_TESTS_C+= getentropy_test
|
||||
ATF_TESTS_C+= getmntinfo_test
|
||||
@@ -92,6 +95,12 @@ TEST_METADATA.setdomainname_test+= is_exclusive=true
|
||||
TESTS_SUBDIRS= execve
|
||||
TESTS_SUBDIRS+= posix_spawn
|
||||
|
||||
# Tests that require blocks support
|
||||
.for t in fts_blocks_test
|
||||
CFLAGS.${t}.c+= -fblocks
|
||||
LIBADD.${t}+= BlocksRuntime
|
||||
.endfor
|
||||
|
||||
# The old testcase name
|
||||
TEST_FNMATCH= test-fnmatch
|
||||
CLEANFILES+= ${GEN_SH_CASE_TESTCASES}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*-
|
||||
* Copyright (c) 2025 Klara, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <fts.h>
|
||||
|
||||
#include <atf-c.h>
|
||||
|
||||
/*
|
||||
* Create two directories with three files each in lexicographical order,
|
||||
* then call FTS with a sort block that sorts in reverse lexicographical
|
||||
* order. This has the least chance of getting a false positive due to
|
||||
* differing file system semantics. UFS will return the files in the
|
||||
* order they were created while ZFS will sort them lexicographically; in
|
||||
* both cases, the order we expect is the reverse.
|
||||
*/
|
||||
ATF_TC_WITHOUT_HEAD(fts_blocks_test);
|
||||
ATF_TC_BODY(fts_blocks_test, tc)
|
||||
{
|
||||
char *args[] = {
|
||||
"bar", "foo", NULL
|
||||
};
|
||||
char *paths[] = {
|
||||
"foo", "z", "y", "x", "foo",
|
||||
"bar", "c", "b", "a", "bar",
|
||||
NULL
|
||||
};
|
||||
char **expect = paths;
|
||||
FTS *fts;
|
||||
FTSENT *ftse;
|
||||
|
||||
ATF_REQUIRE_EQ(0, mkdir("bar", 0755));
|
||||
ATF_REQUIRE_EQ(0, close(creat("bar/a", 0644)));
|
||||
ATF_REQUIRE_EQ(0, close(creat("bar/b", 0644)));
|
||||
ATF_REQUIRE_EQ(0, close(creat("bar/c", 0644)));
|
||||
ATF_REQUIRE_EQ(0, mkdir("foo", 0755));
|
||||
ATF_REQUIRE_EQ(0, close(creat("foo/x", 0644)));
|
||||
ATF_REQUIRE_EQ(0, close(creat("foo/y", 0644)));
|
||||
ATF_REQUIRE_EQ(0, close(creat("foo/z", 0644)));
|
||||
fts = fts_open_b(args, 0,
|
||||
^(const FTSENT * const *a, const FTSENT * const *b) {
|
||||
return (strcmp((*b)->fts_name, (*a)->fts_name));
|
||||
});
|
||||
ATF_REQUIRE_MSG(fts != NULL, "fts_open_b(): %m");
|
||||
while ((ftse = fts_read(fts)) != NULL && *expect != NULL) {
|
||||
ATF_CHECK_STREQ(*expect, ftse->fts_name);
|
||||
expect++;
|
||||
}
|
||||
ATF_CHECK_EQ(NULL, ftse);
|
||||
ATF_CHECK_EQ(NULL, *expect);
|
||||
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
ATF_TP_ADD_TC(tp, fts_blocks_test);
|
||||
return (atf_no_error());
|
||||
}
|
||||
@@ -61,7 +61,7 @@ CFLAGS+= -I${.CURDIR}
|
||||
|
||||
LIBADD.cxa_thread_atexit_test+= pthread
|
||||
|
||||
# Tests that requires Blocks feature
|
||||
# Tests that require blocks support
|
||||
.for t in qsort_b_test
|
||||
CFLAGS.${t}.c+= -fblocks
|
||||
LIBADD.${t}+= BlocksRuntime
|
||||
|
||||
Reference in New Issue
Block a user