about summary refs log tree commit diff
path: root/io
diff options
context:
space:
mode:
Diffstat (limited to 'io')
-rw-r--r--io/Makefile5
-rw-r--r--io/Versions3
-rw-r--r--io/fts.c148
-rw-r--r--io/fts.h96
-rw-r--r--io/fts64.c30
-rw-r--r--io/tst-fts-lfs.c2
-rw-r--r--io/tst-fts.c231
7 files changed, 449 insertions, 66 deletions
diff --git a/io/Makefile b/io/Makefile
index 613dce0124..a9ad919687 100644
--- a/io/Makefile
+++ b/io/Makefile
@@ -49,7 +49,7 @@ routines :=								\
 	ttyname ttyname_r isatty					\
 	link linkat symlink symlinkat readlink readlinkat		\
 	unlink unlinkat rmdir						\
-	ftw ftw64 fts poll ppoll					\
+	ftw ftw64 fts fts64 poll ppoll					\
 	posix_fadvise posix_fadvise64					\
 	posix_fallocate posix_fallocate64				\
 	sendfile sendfile64 \
@@ -71,7 +71,7 @@ tests		:= test-utime test-stat test-stat2 test-lfs tst-getcwd \
 		   tst-renameat tst-fchownat tst-fchmodat tst-faccessat \
 		   tst-symlinkat tst-linkat tst-readlinkat tst-mkdirat \
 		   tst-mknodat tst-mkfifoat tst-ttyname_r bug-ftw5 \
-		   tst-posix_fallocate
+		   tst-posix_fallocate tst-fts tst-fts-lfs
 
 ifeq ($(run-built-tests),yes)
 tests-special += $(objpfx)ftwtest.out
@@ -90,6 +90,7 @@ CFLAGS-fstatfs.c = -fexceptions
 CFLAGS-statvfs.c = -fexceptions
 CFLAGS-fstatvfs.c = -fexceptions
 CFLAGS-fts.c = -Wno-uninitialized $(uses-callbacks) -fexceptions
+CFLAGS-fts64.c = -Wno-uninitialized $(uses-callbacks) -fexceptions
 CFLAGS-ftw.c = $(uses-callbacks) -fexceptions
 CFLAGS-ftw64.c = $(uses-callbacks) -fexceptions
 CFLAGS-lockf.c = -fexceptions
diff --git a/io/Versions b/io/Versions
index 6c0a23b465..64316cd025 100644
--- a/io/Versions
+++ b/io/Versions
@@ -122,4 +122,7 @@ libc {
   GLIBC_2.9 {
     dup3; pipe2;
   }
+  GLIBC_2.23 {
+    fts64_children; fts64_close; fts64_open; fts64_read; fts64_set;
+  }
 }
diff --git a/io/fts.c b/io/fts.c
index f83267611f..e8d32a2327 100644
--- a/io/fts.c
+++ b/io/fts.c
@@ -1,3 +1,21 @@
+/* File tree traversal functions.
+   Copyright (C) 1994-2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
 /*-
  * Copyright (c) 1990, 1993, 1994
  *	The Regents of the University of California.  All rights reserved.
@@ -53,16 +71,30 @@ static char sccsid[] = "@(#)fts.c	8.6 (Berkeley) 8/14/94";
 #endif
 
 
-static FTSENT	*fts_alloc (FTS *, const char *, size_t) internal_function;
-static FTSENT	*fts_build (FTS *, int) internal_function;
-static void	 fts_lfree (FTSENT *) internal_function;
-static void	 fts_load (FTS *, FTSENT *) internal_function;
+/* Support for the LFS API version.  */
+#ifndef FTS_OPEN
+#define FTS_OPEN fts_open
+#define FTS_CLOSE fts_close
+#define FTS_READ fts_read
+#define FTS_SET fts_set
+#define FTS_CHILDREN fts_children
+# define FTSOBJ FTS
+# define FTSENTRY FTSENT
+# define INO_T ino_t
+# define STAT stat
+# define LSTAT lstat
+#endif
+
+static FTSENTRY	*fts_alloc (FTSOBJ *, const char *, size_t) internal_function;
+static FTSENTRY	*fts_build (FTSOBJ *, int) internal_function;
+static void	 fts_lfree (FTSENTRY *) internal_function;
+static void	 fts_load (FTSOBJ *, FTSENTRY *) internal_function;
 static size_t	 fts_maxarglen (char * const *) internal_function;
-static void	 fts_padjust (FTS *, FTSENT *) internal_function;
-static int	 fts_palloc (FTS *, size_t) internal_function;
-static FTSENT	*fts_sort (FTS *, FTSENT *, int) internal_function;
-static u_short	 fts_stat (FTS *, FTSENT *, int) internal_function;
-static int      fts_safe_changedir (FTS *, FTSENT *, int, const char *)
+static void	 fts_padjust (FTSOBJ *, FTSENTRY *) internal_function;
+static int	 fts_palloc (FTSOBJ *, size_t) internal_function;
+static FTSENTRY	*fts_sort (FTSOBJ *, FTSENTRY *, int) internal_function;
+static u_short	 fts_stat (FTSOBJ *, FTSENTRY *, int) internal_function;
+static int      fts_safe_changedir (FTSOBJ *, FTSENTRY *, int, const char *)
      internal_function;
 
 #ifndef MAX
@@ -84,15 +116,15 @@ static int      fts_safe_changedir (FTS *, FTSENT *, int, const char *)
 #define	BNAMES		2		/* fts_children, names only */
 #define	BREAD		3		/* fts_read */
 
-FTS *
-fts_open (char * const *argv, int options,
-	  int (*compar) (const FTSENT **, const FTSENT **))
+FTSOBJ *
+FTS_OPEN (char * const *argv, int options,
+	  int (*compar) (const FTSENTRY **, const FTSENTRY **))
 {
-	FTS *sp;
-	FTSENT *p, *root;
+	FTSOBJ *sp;
+	FTSENTRY *p, *root;
 	int nitems;
-	FTSENT *parent = NULL;
-	FTSENT *tmp;
+	FTSENTRY *parent = NULL;
+	FTSENTRY *tmp;
 
 	/* Options check. */
 	if (options & ~FTS_OPTIONMASK) {
@@ -101,9 +133,9 @@ fts_open (char * const *argv, int options,
 	}
 
 	/* Allocate/initialize the stream */
-	if ((sp = malloc((u_int)sizeof(FTS))) == NULL)
+	if ((sp = malloc((u_int)sizeof(FTSOBJ))) == NULL)
 		return (NULL);
-	memset(sp, 0, sizeof(FTS));
+	memset(sp, 0, sizeof(FTSOBJ));
 	sp->fts_compar = (int (*) (const void *, const void *)) compar;
 	sp->fts_options = options;
 
@@ -200,7 +232,7 @@ mem1:	free(sp);
 
 static void
 internal_function
-fts_load (FTS *sp, FTSENT *p)
+fts_load (FTSOBJ *sp, FTSENTRY *p)
 {
 	int len;
 	char *cp;
@@ -224,9 +256,9 @@ fts_load (FTS *sp, FTSENT *p)
 }
 
 int
-fts_close (FTS *sp)
+FTS_CLOSE (FTSOBJ *sp)
 {
-	FTSENT *freep, *p;
+	FTSENTRY *freep, *p;
 	int saved_errno;
 
 	/*
@@ -276,10 +308,10 @@ fts_close (FTS *sp)
 	(p->fts_path[p->fts_pathlen - 1] == '/'				\
 	    ? p->fts_pathlen - 1 : p->fts_pathlen)
 
-FTSENT *
-fts_read (FTS *sp)
+FTSENTRY *
+FTS_READ (FTSOBJ *sp)
 {
-	FTSENT *p, *tmp;
+	FTSENTRY *p, *tmp;
 	int instr;
 	char *t;
 	int saved_errno;
@@ -473,7 +505,7 @@ name:		t = sp->fts_path + NAPPEND(p->fts_parent);
  */
 /* ARGSUSED */
 int
-fts_set (FTS *sp, FTSENT *p, int instr)
+FTS_SET (FTSOBJ *sp, FTSENTRY *p, int instr)
 {
 	if (instr != 0 && instr != FTS_AGAIN && instr != FTS_FOLLOW &&
 	    instr != FTS_NOINSTR && instr != FTS_SKIP) {
@@ -484,10 +516,10 @@ fts_set (FTS *sp, FTSENT *p, int instr)
 	return (0);
 }
 
-FTSENT *
-fts_children (FTS *sp, int instr)
+FTSENTRY *
+FTS_CHILDREN(FTSOBJ *sp, int instr)
 {
-	FTSENT *p;
+	FTSENTRY *p;
 	int fd;
 
 	if (instr != 0 && instr != FTS_NAMEONLY) {
@@ -574,14 +606,14 @@ dirent_not_directory(const struct dirent *dp)
  * directories and for any files after the subdirectories in the directory have
  * been found, cutting the stat calls by about 2/3.
  */
-static FTSENT *
+static FTSENTRY *
 internal_function
-fts_build (FTS *sp, int type)
+fts_build (FTSOBJ *sp, int type)
 {
 	struct dirent *dp;
-	FTSENT *p, *head;
+	FTSENTRY *p, *head;
 	int nitems;
-	FTSENT *cur, *tail;
+	FTSENTRY *cur, *tail;
 	DIR *dirp;
 	void *oldaddr;
 	int cderrno, descend, len, level, nlinks, saved_errno,
@@ -839,12 +871,12 @@ mem1:				saved_errno = errno;
 
 static u_short
 internal_function
-fts_stat (FTS *sp, FTSENT *p, int follow)
+fts_stat (FTSOBJ *sp, FTSENTRY *p, int follow)
 {
-	FTSENT *t;
+	FTSENTRY *t;
 	dev_t dev;
-	ino_t ino;
-	struct stat *sbp, sb;
+	INO_T ino;
+	struct STAT *sbp, sb;
 	int saved_errno;
 
 	/* If user needs stat info, stat buffer already allocated. */
@@ -867,18 +899,18 @@ fts_stat (FTS *sp, FTSENT *p, int follow)
 	 * fail, set the errno from the stat call.
 	 */
 	if (ISSET(FTS_LOGICAL) || follow) {
-		if (stat(p->fts_accpath, sbp)) {
+		if (STAT(p->fts_accpath, sbp)) {
 			saved_errno = errno;
-			if (!lstat(p->fts_accpath, sbp)) {
+			if (!LSTAT(p->fts_accpath, sbp)) {
 				__set_errno (0);
 				return (FTS_SLNONE);
 			}
 			p->fts_errno = saved_errno;
 			goto err;
 		}
-	} else if (lstat(p->fts_accpath, sbp)) {
+	} else if (LSTAT(p->fts_accpath, sbp)) {
 		p->fts_errno = errno;
-err:		memset(sbp, 0, sizeof(struct stat));
+err:		memset(sbp, 0, sizeof(struct STAT));
 		return (FTS_NS);
 	}
 
@@ -918,11 +950,11 @@ err:		memset(sbp, 0, sizeof(struct stat));
 	return (FTS_DEFAULT);
 }
 
-static FTSENT *
+static FTSENTRY *
 internal_function
-fts_sort (FTS *sp, FTSENT *head, int nitems)
+fts_sort (FTSOBJ *sp, FTSENTRY *head, int nitems)
 {
-	FTSENT **ap, *p;
+	FTSENTRY **ap, *p;
 
 	/*
 	 * Construct an array of pointers to the structures and call qsort(3).
@@ -932,11 +964,11 @@ fts_sort (FTS *sp, FTSENT *head, int nitems)
 	 * 40 so don't realloc one entry at a time.
 	 */
 	if (nitems > sp->fts_nitems) {
-		struct _ftsent **a;
+		FTSENTRY **a;
 
 		sp->fts_nitems = nitems + 40;
 		if ((a = realloc(sp->fts_array,
- 		    (size_t)(sp->fts_nitems * sizeof(FTSENT *)))) == NULL) {
+		    (size_t)(sp->fts_nitems * sizeof(FTSENTRY *)))) == NULL) {
 			free(sp->fts_array);
 			sp->fts_array = NULL;
 			sp->fts_nitems = 0;
@@ -946,18 +978,18 @@ fts_sort (FTS *sp, FTSENT *head, int nitems)
 	}
 	for (ap = sp->fts_array, p = head; p; p = p->fts_link)
 		*ap++ = p;
-	qsort((void *)sp->fts_array, nitems, sizeof(FTSENT *), sp->fts_compar);
+	qsort((void *)sp->fts_array, nitems, sizeof(FTSENTRY *), sp->fts_compar);
 	for (head = *(ap = sp->fts_array); --nitems; ++ap)
 		ap[0]->fts_link = ap[1];
 	ap[0]->fts_link = NULL;
 	return (head);
 }
 
-static FTSENT *
+static FTSENTRY *
 internal_function
-fts_alloc (FTS *sp, const char *name, size_t namelen)
+fts_alloc (FTSOBJ *sp, const char *name, size_t namelen)
 {
-	FTSENT *p;
+	FTSENTRY *p;
 	size_t len;
 
 	/*
@@ -968,9 +1000,9 @@ fts_alloc (FTS *sp, const char *name, size_t namelen)
 	 * fts_name field is declared to be of size 1, the fts_name pointer is
 	 * namelen + 2 before the first possible address of the stat structure.
 	 */
-	len = sizeof(FTSENT) + namelen;
+	len = sizeof(FTSENTRY) + namelen;
 	if (!ISSET(FTS_NOSTAT))
-		len += sizeof(struct stat) + ALIGNBYTES;
+		len += sizeof(struct STAT) + ALIGNBYTES;
 	if ((p = malloc(len)) == NULL)
 		return (NULL);
 
@@ -979,7 +1011,7 @@ fts_alloc (FTS *sp, const char *name, size_t namelen)
 	p->fts_name[namelen] = '\0';
 
 	if (!ISSET(FTS_NOSTAT))
-		p->fts_statp = (struct stat *)ALIGN(p->fts_name + namelen + 2);
+		p->fts_statp = (struct STAT *)ALIGN(p->fts_name + namelen + 2);
 	p->fts_namelen = namelen;
 	p->fts_path = sp->fts_path;
 	p->fts_errno = 0;
@@ -992,9 +1024,9 @@ fts_alloc (FTS *sp, const char *name, size_t namelen)
 
 static void
 internal_function
-fts_lfree (FTSENT *head)
+fts_lfree (FTSENTRY *head)
 {
-	FTSENT *p;
+	FTSENTRY *p;
 
 	/* Free a linked list of structures. */
 	while ((p = head)) {
@@ -1011,7 +1043,7 @@ fts_lfree (FTSENT *head)
  */
 static int
 internal_function
-fts_palloc (FTS *sp, size_t more)
+fts_palloc (FTSOBJ *sp, size_t more)
 {
 	char *p;
 
@@ -1043,9 +1075,9 @@ fts_palloc (FTS *sp, size_t more)
  */
 static void
 internal_function
-fts_padjust (FTS *sp, FTSENT *head)
+fts_padjust (FTSOBJ *sp, FTSENTRY *head)
 {
-	FTSENT *p;
+	FTSENTRY *p;
 	char *addr = sp->fts_path;
 
 #define	ADJUST(p) do {							\
@@ -1085,7 +1117,7 @@ fts_maxarglen (char * const *argv)
  */
 static int
 internal_function
-fts_safe_changedir (FTS *sp, FTSENT *p, int fd, const char *path)
+fts_safe_changedir (FTSOBJ *sp, FTSENTRY *p, int fd, const char *path)
 {
 	int ret, oerrno, newfd;
 	struct stat64 sb;
diff --git a/io/fts.h b/io/fts.h
index 0a070ba8dc..2185e2386c 100644
--- a/io/fts.h
+++ b/io/fts.h
@@ -1,3 +1,21 @@
+/* File tree traversal functions declarations.
+   Copyright (C) 1994-2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
 /*
  * Copyright (c) 1989, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -35,12 +53,6 @@
 #include <features.h>
 #include <sys/types.h>
 
-/* The fts interface is incompatible with the LFS interface which
-   transparently uses the 64-bit file access functions.  */
-#ifdef __USE_FILE_OFFSET64
-# error "<fts.h> cannot be used with -D_FILE_OFFSET_BITS==64"
-#endif
-
 
 typedef struct {
 	struct _ftsent *fts_cur;	/* current node */
@@ -68,6 +80,21 @@ typedef struct {
 	int fts_options;		/* fts_open options, global flags */
 } FTS;
 
+#ifdef __USE_LARGEFILE64
+typedef struct {
+	struct _ftsent64 *fts_cur;	/* current node */
+	struct _ftsent64 *fts_child;	/* linked list of children */
+	struct _ftsent64 **fts_array;	/* sort array */
+	dev_t fts_dev;			/* starting device # */
+	char *fts_path;			/* path for this descent */
+	int fts_rfd;			/* fd for root */
+	int fts_pathlen;		/* sizeof(path) */
+	int fts_nitems;			/* elements in the sort array */
+	int (*fts_compar) (const void *, const void *); /* compare fn */
+	int fts_options;		/* fts_open options, global flags */
+} FTS64;
+#endif
+
 typedef struct _ftsent {
 	struct _ftsent *fts_cycle;	/* cycle node */
 	struct _ftsent *fts_parent;	/* parent directory */
@@ -119,13 +146,70 @@ typedef struct _ftsent {
 	char fts_name[1];		/* file name */
 } FTSENT;
 
+#ifdef __USE_LARGEFILE64
+typedef struct _ftsent64 {
+	struct _ftsent64 *fts_cycle;	/* cycle node */
+	struct _ftsent64 *fts_parent;	/* parent directory */
+	struct _ftsent64 *fts_link;	/* next file in directory */
+	long fts_number;	        /* local numeric value */
+	void *fts_pointer;	        /* local address value */
+	char *fts_accpath;		/* access path */
+	char *fts_path;			/* root path */
+	int fts_errno;			/* errno for this node */
+	int fts_symfd;			/* fd for symlink */
+	u_short fts_pathlen;		/* strlen(fts_path) */
+	u_short fts_namelen;		/* strlen(fts_name) */
+
+	ino64_t fts_ino;		/* inode */
+	dev_t fts_dev;			/* device */
+	nlink_t fts_nlink;		/* link count */
+
+	short fts_level;		/* depth (-1 to N) */
+
+	u_short fts_info;		/* user flags for FTSENT structure */
+
+	u_short fts_flags;		/* private flags for FTSENT structure */
+
+	u_short fts_instr;		/* fts_set() instructions */
+
+	struct stat64 *fts_statp;	/* stat(2) information */
+	char fts_name[1];		/* file name */
+} FTSENT64;
+#endif
+
 __BEGIN_DECLS
+#ifndef __USE_FILE_OFFSET64
 FTSENT	*fts_children (FTS *, int);
 int	 fts_close (FTS *);
 FTS	*fts_open (char * const *, int,
 		   int (*)(const FTSENT **, const FTSENT **));
 FTSENT	*fts_read (FTS *);
 int	 fts_set (FTS *, FTSENT *, int) __THROW;
+#else
+# ifdef __REDIRECT
+FTSENT	*__REDIRECT (fts_children, (FTS *, int), fts64_children);
+int	 __REDIRECT (fts_close, (FTS *), fts64_close);
+FTS	*__REDIRECT (fts_open, (char * const *, int,
+				int (*)(const FTSENT **, const FTSENT **)),
+		     fts64_open);
+FTSENT	*__REDIRECT (fts_read, (FTS *), fts64_read);
+int	 __REDIRECT (fts_set, (FTS *, FTSENT *, int), fts64_set) __THROW;
+# else
+#  define fts_children fts64_children
+#  define fts_close fts64_close
+#  define fts_open fts64_open
+#  define fts_read fts64_read
+#  define fts_set fts64_set
+# endif
+#endif
+#ifdef __USE_LARGEFILE64
+FTSENT64 *fts64_children (FTS64 *, int);
+int	  fts64_close (FTS64 *);
+FTS64	 *fts64_open (char * const *, int,
+		      int (*)(const FTSENT64 **, const FTSENT64 **));
+FTSENT64 *fts64_read (FTS64 *);
+int	 fts64_set (FTS64 *, FTSENT64 *, int) __THROW;
+#endif
 __END_DECLS
 
 #endif /* fts.h */
diff --git a/io/fts64.c b/io/fts64.c
new file mode 100644
index 0000000000..bddf8c63d6
--- /dev/null
+++ b/io/fts64.c
@@ -0,0 +1,30 @@
+/* File tree traversal functions LFS version.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#define FTS_OPEN fts64_open
+#define FTS_CLOSE fts64_close
+#define FTS_READ fts64_read
+#define FTS_SET fts64_set
+#define FTS_CHILDREN fts64_children
+#define FTSOBJ FTS64
+#define FTSENTRY FTSENT64
+#define INO_T ino64_t
+#define STAT stat64
+#define LSTAT lstat64
+
+#include "fts.c"
diff --git a/io/tst-fts-lfs.c b/io/tst-fts-lfs.c
new file mode 100644
index 0000000000..bc169342f8
--- /dev/null
+++ b/io/tst-fts-lfs.c
@@ -0,0 +1,2 @@
+#define _FILE_OFFSET_BITS 64
+#include "tst-fts.c"
diff --git a/io/tst-fts.c b/io/tst-fts.c
new file mode 100644
index 0000000000..efe225d6c0
--- /dev/null
+++ b/io/tst-fts.c
@@ -0,0 +1,231 @@
+/* Simple test for some fts functions.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fts.h>
+
+#include <errno.h>
+#include <error.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void prepare (void);
+static int do_test (void);
+#define PREPARE(argc, argv)     prepare ()
+#define TEST_FUNCTION           do_test ()
+#include "../test-skeleton.c"
+
+static char *fts_test_dir;
+
+static void
+make_dir (const char *dirname)
+{
+  char *name;
+  if (asprintf (&name, "%s/%s", fts_test_dir, dirname) < 0)
+    {
+      puts ("out of memory");
+      exit (1);
+    }
+
+  if (mkdir (name, 0700) < 0)
+    {
+      printf ("cannot create dir \"%s\": %m\n", name);
+      exit (1);
+    }
+
+  add_temp_file (name);
+}
+
+static void
+make_file (const char *filename)
+{
+  char *name;
+  if (asprintf (&name, "%s/%s", fts_test_dir, filename) < 0)
+    {
+      puts ("out of memory");
+      exit (1);
+    }
+
+  int fd = open (name, O_WRONLY | O_CREAT | O_EXCL, 0600);
+  if (fd < 0)
+    {
+      printf ("cannot create file \"%s\": %m\n", name);
+      exit (1);
+    }
+  close (fd);
+
+  add_temp_file (name);
+}
+
+static void
+prepare (void)
+{
+  char *dirbuf;
+  char dir_name[] = "/tst-fts.XXXXXX";
+
+  if (asprintf (&dirbuf, "%s%s", test_dir, dir_name) < 0)
+    {
+      puts ("out of memory");
+      exit (1);
+    }
+
+  if (mkdtemp (dirbuf) == NULL)
+    {
+      puts ("cannot create temporary directory");
+      exit (1);
+    }
+
+  add_temp_file (dirbuf);
+  fts_test_dir = dirbuf;
+
+  make_file ("12");
+  make_file ("345");
+  make_file ("6789");
+
+  make_dir ("aaa");
+  make_file ("aaa/1234");
+  make_file ("aaa/5678");
+
+  make_dir ("bbb");
+  make_file ("bbb/1234");
+  make_file ("bbb/5678");
+  make_file ("bbb/90ab");
+}
+
+/* Largest name wins, otherwise strcmp.  */
+static int
+compare_ents (const FTSENT **ent1, const FTSENT **ent2)
+{
+  short len1 = (*ent1)->fts_namelen;
+  short len2 = (*ent2)->fts_namelen;
+  if (len1 != len2)
+    return len1 - len2;
+  else
+    {
+      const char *name1 = (*ent1)->fts_name;
+      const char *name2 = (*ent2)->fts_name;
+      return strcmp (name1, name2);
+    }
+}
+
+/* Count the number of files seen as children.  */
+static int files = 0;
+
+static void
+children (FTS *fts)
+{
+  FTSENT *child = fts_children (fts, 0);
+  if (child == NULL && errno != 0)
+    {
+      printf ("FAIL: fts_children: %m\n");
+      exit (1);
+    }
+
+  while (child != NULL)
+    {
+      short level = child->fts_level;
+      const char *name = child->fts_name;
+      if (child->fts_info == FTS_F || child->fts_info == FTS_NSOK)
+	{
+	  files++;
+	  printf ("%*s%s\n", 2 * level, "", name);
+	}
+      child = child->fts_link;
+    }
+}
+
+/* Count the number of dirs seen in the test.  */
+static int dirs = 0;
+
+static int
+do_test (void)
+{
+  char *paths[2] = { fts_test_dir, NULL };
+  FTS *fts;
+  fts = fts_open (paths, FTS_LOGICAL, &compare_ents);
+  if (fts == NULL)
+    {
+      printf ("FAIL: fts_open: %m\n");
+      exit (1);
+    }
+
+  FTSENT *ent;
+  while ((ent = fts_read (fts)) != NULL)
+    {
+      const char *name = ent->fts_name;
+      short level = ent->fts_level;
+      switch (ent->fts_info)
+	{
+	case FTS_F:
+	  /* Don't show anything, children will have on parent dir.  */
+	  break;
+
+	case FTS_D:
+	  printf ("%*s%s =>\n", 2 * level, "", name);
+	  children (fts);
+	  break;
+
+	case FTS_DP:
+	  dirs++;
+	  printf ("%*s<= %s\n", 2 * level, "", name);
+	  break;
+
+	case FTS_NS:
+	case FTS_ERR:
+	  printf ("FAIL: fts_read ent: %s\n", strerror (ent->fts_errno));
+	  exit (1);
+	  break;
+
+	default:
+	  printf ("FAIL: unexpected fts_read ent %s\n", name);
+	  exit (1);
+	  break;
+	}
+    }
+  /* fts_read returns NULL when done (and clears errno)
+     or when an error occured (with errno set).  */
+  if (errno != 0)
+    {
+      printf ("FAIL: fts_read: %m\n");
+      exit (1);
+    }
+
+  if (fts_close (fts) != 0)
+    {
+      printf ("FAIL: fts_close: %m\n");
+      exit (1);
+    }
+
+  if (files != 8)
+    {
+      printf ("FAIL: Unexpected number of files: %d\n", files);
+      return 1;
+    }
+
+  if (dirs != 3)
+    {
+      printf ("FAIL: Unexpected number of dirs: %d\n", dirs);
+      return 1;
+    }
+
+  puts ("PASS");
+  return 0;
+}