From patchwork Thu Feb 12 19:31:03 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Adhemerval Zanella X-Patchwork-Id: 130047 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id C95844BA2E16 for ; Thu, 12 Feb 2026 19:32:48 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org C95844BA2E16 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=ptA4W97O X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-yw1-x112b.google.com (mail-yw1-x112b.google.com [IPv6:2607:f8b0:4864:20::112b]) by sourceware.org (Postfix) with ESMTPS id 018CF4B9DB49 for ; Thu, 12 Feb 2026 19:31:49 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 018CF4B9DB49 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 018CF4B9DB49 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::112b ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1770924710; cv=none; b=QYa8VMfshASoXNFh2FhwryC9dSD775KXOin/T3RqUJ3yNF3iiSkAjiCS7dMfr383Cb0vaXN5N6JTXx3APyXeh9qCWjD1SZnx3adeMz4mPA26JCAniyXdpJc72uNLxWTevHTypgkov15hcGVV8C98J248yMbVdtkVu+NPQ4Aqit8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1770924710; c=relaxed/simple; bh=K6wiJIwwSJ/gKIT4XrJ9WbZEyzdcvUeglnOjLbHQXqY=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=IhUe+odJWOos/8w7jWPqCnbjuAzrW2lUeD+rpGWS7hxL8bS5+7SL/1BL08EjhVRuSx1GPbjyjGhCjNHQZ+WgCUeDYWFNqRT4TqFtSu/ylsB3yUP+5PUWPXR2um+XfK1zjDxx89hH0iL5Ndvinfat7GmhI1uB9J6dx0oetAo+DAk= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 018CF4B9DB49 Received: by mail-yw1-x112b.google.com with SMTP id 00721157ae682-794911acb04so3155347b3.0 for ; Thu, 12 Feb 2026 11:31:49 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1770924709; x=1771529509; darn=sourceware.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=Dm22P9puT/L+SG7xFqdqfYOMBG2n0qVLadZJgcwLurU=; b=ptA4W97O8Ax7jZq2w5nmy6kFgG/z09ZqG3s5/pFcgA6ccgMd2E11+cFsMu7Cpl2/EG eCj+xsoq/71pvQyPIC2VLJfRf7SdbO6RqnBlgkenuChL9j2oOfRN7DM6VR2fUK1Lmkas pxeJ5F5Gi42rJWC/2YHoOg0HX3JwA0Z8/AOS0V1warjzE7y4tWDXVqluT+WMn3GnVR0g A7dJu0lis3zhrenOW8mgJNmEYISQk9S9Wfz/rs9oL3YiDD7V0J7Z+Wn9JubiedHf7lH5 or1gutE/2pLSQxL5U1uhhKksQczarXM+VPuIdI7DuqO/xVpJFbB0KcoLYy4/AjpB/3Ya yLXA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1770924709; x=1771529509; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=Dm22P9puT/L+SG7xFqdqfYOMBG2n0qVLadZJgcwLurU=; b=lF6Hca63cWNJ4JwWi+dH3IFIKGjuKaPFx08VnhGBLXDuxA15fZ2MDZEx9slgcZ3kmV Pm+EqHCH7B3YRHvQCteW+IJWAVoLt8+UH5rFJlBRXapdXJOkFaoqLEci04RjvRc5hUZm SrW7HVwtrgw0nP237RIR9cPw7JdmAHwpnn6Var22gpkBronFcRapasp+yvoocxh+rgP9 bsu8JjhoFWXGHZfBY4ATFig0xVty1YiFW/5c7ISJ7uyckBvQqh4ij3vdU+Ln+MdRetgw jZifGuYSfpWIaMu0MauFoTui/QtCPcj2PMgLzSmpMwUVTmA9KoHMI1D3ZPwhMVXbjsGU enNg== X-Gm-Message-State: AOJu0YyuIqmzbbAIGsBsZaNwyWyEh06c6MTXJ8TFYhvkcoP4QZnX9+i8 PHBzAOUPCxgcl/+j15TniPmHg8UIDlRZsn0WMLDU55Wwf2ukJpj95KHHlGJIGmrNlEwhe5psHH5 oQ+NTWdo= X-Gm-Gg: AZuq6aK1H/wiloZBxBzuIQpjNaMLwOxwelt2aluVtfGEr08jhWyJHnirj9jNoaFiqdU FIz+AWLFSBzahaq+frww0CegVCQ0bu7mSciSXe5vQiEtgQQQ1VXZ9rsYOp5gZl/0+aCge9iZz6A OKe9r38M0RUFqWVjMEIIafa6Rukja+mRS3WfqTvTfAkWfOS5kFTOObQyqMjJaryqyQ8niHhWvTv tFdYv3vhjmPVkaZtx41KfyuHIS7T/iq+28L8279BqKXQs8t1kAV0hnDhRD2y2gNRat51xcUph44 RF4yPte5DzVS5U0MI3Up7S36hbZN3yJhC31z9jm0axFc35ll0KRUDL7ckcEC8gDz/hOzcPAAMfL zS5u3IWq4FU+DwDzJgDQI56QoCOsSLojhkb2zyHNf4N55+qCRjcvBNUl2Hgmp5V4FajOQLZrVc8 Tapt29pa2Dh1panM8uqjk9wFlASOylf1NRWw== X-Received: by 2002:a05:690c:660e:b0:796:6d82:10a9 with SMTP id 00721157ae682-7972f0f7367mr40202227b3.15.1770924707929; Thu, 12 Feb 2026 11:31:47 -0800 (PST) Received: from mandiga.. ([2804:1b3:a7c2:42d3:9b5:1b53:5be8:fe60]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7966c254daasm52828987b3.44.2026.02.12.11.31.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 12 Feb 2026 11:31:47 -0800 (PST) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: Siddhesh Poyarekar , Carlos O'Donell Subject: [PATCH] io: Refactor {n}ftw to use fts for stack safety and conformance (BZ 33882) Date: Thu, 12 Feb 2026 16:31:03 -0300 Message-ID: <20260212193142.1010441-1-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 X-Spam-Status: No, score=-8.7 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, KAM_SOMETLD_ARE_BAD_TLD, PDS_OTHER_BAD_TLD, RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE, SPF_PASS, TXREP, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org The current implementation of nftw uses recursive function calls to traverse the directory tree. This approach is susceptible to stack overflow errors when traversing deeply nested directory structures, which can occur in legitimate workloads or be triggered by malicious inputs. This patch reimplements nftw on top of fts, which uses an iterative approach with limited stack usage. However, fts semantics differ slightly from glibc nftw requirements, which require some additional handling: * Physical Walk (FTW_PHYS) Symlink Handling, where fts checks symlink targets even in physical mode (FTS_PHYS), returning FTS_SLNONE for broken links. This requires explicitly mapping both FTS_SL and FTS_SLNONE to FTW_SL. nftw (physical) must report the link itself, regardless of the target's validity. * Logical Walk (FTS_LOGICAL) and Cycles, where fts reports directory cycles as FTS_DC. These are mapped to FTW_SLN (Symbolic Link that causes a cycle/cannot be followed), matching historical nftw behavior. * Mount Point Crossings (FTW_MOUNT), where FTS_XDEV stops fts from descending into mount points. In FTW_DEPTH mode, nftw requires the mount point directory itself to be reported, and since fts never enters the directory, it never generates the FTS_DP (post-order) event. This is fixed by capturing the root device ID, and if the directory is a mount point (different device ID), we treat the FTS_D event as the required visit and report it as FTW_DP. * Directory Changing (FTW_CHDIR), where FTS_LOGICAL implies FTS_NOCHDIR in fts, preventing automatic directory changes. This is implemented by forcing FTS_NOCHDIR for all modes to maintain consistent fts behavior and then manually managing FTW_CHDIR. * Root Access Errors, where fts_read returns FTS_NS if the root path cannot be accessed, while ftw expects a hard failure (-1) for permission errors on the root. This is fixed by an explicit check for FTS_ROOTLEVEL. * FTW_ACTIONRETVAL support, where it requires to map the FTW_SKIP_SUBTREE and FTW_SKIP_SIBLINGS to specific logic paths. This change unifies the traversal logic and eliminates the recursion limit for file tree walks. Also added tests for FTW_DNR, which is current missing. Checked on x86_64-linux-gnu and i686-linux-gnu. I also checked with the LTP nftw tests. --- include/fts.h | 50 ++ io/Makefile | 1 + io/fts.c | 41 +- io/fts64.c | 1 + io/ftw.c | 996 +++++++++++--------------------------- io/ftw64-time64.c | 10 +- io/ftw64.c | 10 +- io/ftwtest-sh | 13 + io/tst-nftw-bz33882.c | 88 ++++ sysdeps/wordsize-64/fts.c | 10 +- 10 files changed, 496 insertions(+), 724 deletions(-) create mode 100644 io/tst-nftw-bz33882.c diff --git a/include/fts.h b/include/fts.h index ea36a9b9be..4a34d9357a 100644 --- a/include/fts.h +++ b/include/fts.h @@ -47,6 +47,56 @@ typedef struct _ftsent64_time64 } FSTENT64_TIME64; # endif + +__typeof (fts_open) __fts_open; +libc_hidden_proto (__fts_open); +__typeof (fts64_open) __fts64_open; +libc_hidden_proto (__fts64_open); +__typeof (fts64_open) __fts64_open; +libc_hidden_proto (__fts64_open); +#if __TIMESIZE != 64 +extern FTS64_TIME64* __fts64_open_time64 (char *const*, int, + int (*)(const FSTENT64_TIME64 **, + const FSTENT64_TIME64 **)); +libc_hidden_proto (__fts64_open_time64) +#endif + +__typeof (fts_close) __fts_close; +libc_hidden_proto (__fts_close); +__typeof (fts64_close) __fts64_close; +libc_hidden_proto (__fts64_close); +#if __TIMESIZE != 64 +extern int __fts64_close_time64 (FTS64_TIME64 *); +libc_hidden_proto (__fts64_close_time64) +#endif + +__typeof (fts_read) __fts_read; +libc_hidden_proto (__fts_read); +__typeof (fts64_read) __fts64_read; +libc_hidden_proto (__fts64_read); +#if __TIMESIZE != 64 +extern FSTENT64_TIME64* __fts64_read_time64 (FTS64_TIME64 *); +libc_hidden_proto (__fts64_read_time64) +#endif + +__typeof (fts_set) __fts_set; +libc_hidden_proto (__fts_set); +__typeof (fts64_set) __fts64_set; +libc_hidden_proto (__fts64_set); +#if __TIMESIZE != 64 +extern int __fts64_set_time64 (FTS64_TIME64 *, FSTENT64_TIME64 *, int); +libc_hidden_proto (__fts64_set_time64) +#endif + +__typeof (fts_children) __fts_children; +libc_hidden_proto (__fts_children); +__typeof (fts64_children) __fts64_children; +libc_hidden_proto (__fts64_children); +#if __TIMESIZE != 64 +extern FSTENT64_TIME64* __fts64_children_time64 (FTS64_TIME64 *, int); +libc_hidden_proto (__fts64_children_time64) +#endif + #endif #endif /* _FTS_H */ diff --git a/io/Makefile b/io/Makefile index 707161e10b..80e50578b2 100644 --- a/io/Makefile +++ b/io/Makefile @@ -214,6 +214,7 @@ tests := \ tst-mkdirat \ tst-mkfifoat \ tst-mknodat \ + tst-nftw-bz33882 \ tst-open-tmpfile \ tst-openat \ tst-posix_fallocate \ diff --git a/io/fts.c b/io/fts.c index 27a15b1104..3288d2e0fc 100644 --- a/io/fts.c +++ b/io/fts.c @@ -74,11 +74,11 @@ static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94"; /* 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 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 @@ -86,6 +86,20 @@ static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94"; # define STAT __stat # define LSTAT __lstat # define FSTAT __fstat +# define FTS_INTERNAL_ALIAS +#endif + +#ifdef FTS_INTERNAL_ALIAS +# define _CONCAT(__x, __y) __CONCAT (__x, __y) +# define FTS_INTERNAL_FUNC(__name) _CONCAT (__, __name) +# define FTS_INTERNAL(__name) FTS_INTERNAL_FUNC (__name) +# define FTS_HIDDEN_DEF(__name) \ + weak_alias (FTS_INTERNAL_FUNC (__name), __name); \ + libc_hidden_def (FTS_INTERNAL_FUNC (__name)); +#else +# define FTS_INTERNAL(__name) __name +# define FTS_HIDDEN_DEF(__name) \ + libc_hidden_def (__name); #endif static FTSENTRY *fts_alloc (FTSOBJ *, const char *, size_t); @@ -119,8 +133,8 @@ static int fts_safe_changedir (FTSOBJ *, FTSENTRY *, int, const char *); #define BREAD 3 /* fts_read */ FTSOBJ * -FTS_OPEN (char * const *argv, int options, - int (*compar) (const FTSENTRY **, const FTSENTRY **)) +FTS_INTERNAL(FTS_OPEN) (char * const *argv, int options, + int (*compar) (const FTSENTRY **, const FTSENTRY **)) { FTSOBJ *sp; FTSENTRY *p, *root; @@ -231,6 +245,7 @@ mem2: free(sp->fts_path); mem1: free(sp); return (NULL); } +FTS_HIDDEN_DEF (FTS_OPEN); static void fts_load (FTSOBJ *sp, FTSENTRY *p) @@ -257,7 +272,7 @@ fts_load (FTSOBJ *sp, FTSENTRY *p) } int -FTS_CLOSE (FTSOBJ *sp) +FTS_INTERNAL (FTS_CLOSE) (FTSOBJ *sp) { FTSENTRY *freep, *p; int saved_errno; @@ -300,6 +315,7 @@ FTS_CLOSE (FTSOBJ *sp) free(sp); return (0); } +FTS_HIDDEN_DEF (FTS_CLOSE) /* * Special case of "/" at the end of the path so that slashes aren't @@ -310,7 +326,7 @@ FTS_CLOSE (FTSOBJ *sp) ? p->fts_pathlen - 1 : p->fts_pathlen) FTSENTRY * -FTS_READ (FTSOBJ *sp) +FTS_INTERNAL (FTS_READ) (FTSOBJ *sp) { FTSENTRY *p, *tmp; int instr; @@ -497,6 +513,7 @@ name: t = sp->fts_path + NAPPEND(p->fts_parent); p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP; return p; } +FTS_HIDDEN_DEF (FTS_READ) /* * Fts_set takes the stream as an argument although it's not used in this @@ -506,7 +523,7 @@ name: t = sp->fts_path + NAPPEND(p->fts_parent); */ /* ARGSUSED */ int -FTS_SET (FTSOBJ *sp, FTSENTRY *p, int instr) +FTS_INTERNAL (FTS_SET) (FTSOBJ *sp, FTSENTRY *p, int instr) { if (instr != 0 && instr != FTS_AGAIN && instr != FTS_FOLLOW && instr != FTS_NOINSTR && instr != FTS_SKIP) { @@ -516,9 +533,10 @@ FTS_SET (FTSOBJ *sp, FTSENTRY *p, int instr) p->fts_instr = instr; return (0); } +FTS_HIDDEN_DEF (FTS_SET) FTSENTRY * -FTS_CHILDREN(FTSOBJ *sp, int instr) +FTS_INTERNAL(FTS_CHILDREN)(FTSOBJ *sp, int instr) { FTSENTRY *p; int fd; @@ -582,6 +600,7 @@ FTS_CHILDREN(FTSOBJ *sp, int instr) (void)__close(fd); return (sp->fts_child); } +FTS_HIDDEN_DEF (FTS_CHILDREN) static inline int dirent_not_directory(const struct dirent *dp) diff --git a/io/fts64.c b/io/fts64.c index 152910018e..8cb433aa1a 100644 --- a/io/fts64.c +++ b/io/fts64.c @@ -28,5 +28,6 @@ #define STAT __stat64 #define LSTAT __lstat64 #define FSTAT __fstat64 +#define FTS_INTERNAL_ALIAS #include "fts.c" diff --git a/io/ftw.c b/io/ftw.c index d29734813d..14161d2ae3 100644 --- a/io/ftw.c +++ b/io/ftw.c @@ -16,116 +16,16 @@ License along with the GNU C Library; if not, see . */ -#ifdef HAVE_CONFIG_H -# include -#endif - -#if __GNUC__ -# define alloca __builtin_alloca -#else -# if HAVE_ALLOCA_H -# include -# else -# ifdef _AIX - # pragma alloca -# else -char *alloca (); -# endif -# endif -#endif - -#ifdef _LIBC -# include -# define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent) -#else -# if HAVE_DIRENT_H -# include -# define NAMLEN(dirent) strlen ((dirent)->d_name) -# else -# define dirent direct -# define NAMLEN(dirent) (dirent)->d_namlen -# if HAVE_SYS_NDIR_H -# include -# endif -# if HAVE_SYS_DIR_H -# include -# endif -# if HAVE_NDIR_H -# include -# endif -# endif -#endif - #include #include +#include #include -#include +#include #include +#include #include #include #include -#include -#include -#ifdef _LIBC -# include -#else -# include -#endif - -#if ! _LIBC && !HAVE_DECL_STPCPY && !defined stpcpy -char *stpcpy (); -#endif - -#if ! _LIBC && ! defined HAVE_MEMPCPY && ! defined mempcpy -/* Be CAREFUL that there are no side effects in N. */ -# define mempcpy(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N))) -#endif - -/* #define NDEBUG 1 */ -#include - -#ifndef _LIBC -# undef __chdir -# define __chdir chdir -# undef __closedir -# define __closedir closedir -# undef __fchdir -# define __fchdir fchdir -# undef __getcwd -# define __getcwd(P, N) xgetcwd () -extern char *xgetcwd (void); -# undef __mempcpy -# define __mempcpy mempcpy -# undef __opendir -# define __opendir opendir -# undef __readdir64 -# define __readdir64 readdir -# undef __stpcpy -# define __stpcpy stpcpy -# undef __tdestroy -# define __tdestroy tdestroy -# undef __tfind -# define __tfind tfind -# undef __tsearch -# define __tsearch tsearch -# undef dirent64 -# define dirent64 dirent -# undef MAX -# define MAX(a, b) ((a) > (b) ? (a) : (b)) -#endif - -/* Arrange to make lstat calls go through the wrapper function - on systems with an lstat function that does not dereference symlinks - that are specified with a trailing slash. */ -#if ! _LIBC && ! LSTAT_FOLLOWS_SLASHED_SYMLINK -int rpl_lstat (const char *, struct stat *); -# undef lstat -# define lstat(Name, Stat_buf) rpl_lstat(Name, Stat_buf) -#endif - -#ifndef __set_errno -# define __set_errno(Val) errno = (Val) -#endif /* Support for the LFS API version. */ #ifndef FTW_NAME @@ -135,107 +35,24 @@ int rpl_lstat (const char *, struct stat *); # define NFTW_NEW_NAME __new_nftw # define INO_T ino_t # define STRUCT_STAT stat -# ifdef _LIBC -# define LSTAT __lstat -# define STAT __stat -# define FSTATAT __fstatat -# else -# define LSTAT lstat -# define XTAT stat -# define FSTATAT fstatat -# endif # define FTW_FUNC_T __ftw_func_t # define NFTW_FUNC_T __nftw_func_t #endif - -/* We define PATH_MAX if the system does not provide a definition. - This does not artificially limit any operation. PATH_MAX is simply - used as a guesstimate for the expected maximal path length. - Buffers will be enlarged if necessary. */ -#ifndef PATH_MAX -# define PATH_MAX 1024 +#ifndef FTS_TYPE +# define FTS_TYPE FTS +# define FTSENT_TYPE FTSENT +# define FTS_OPEN __fts_open +# define FTS_READ __fts_read +# define FTS_SET __fts_set +# define FTS_CLOSE __fts_close #endif -struct dir_data -{ - DIR *stream; - int streamfd; - char *content; -}; - struct known_object { dev_t dev; INO_T ino; }; -struct ftw_data -{ - /* Array with pointers to open directory streams. */ - struct dir_data **dirstreams; - size_t actdir; - size_t maxdir; - - /* Buffer containing name of currently processed object. */ - char *dirbuf; - size_t dirbufsize; - - /* Passed as fourth argument to `nftw' callback. The `base' member - tracks the content of the `dirbuf'. */ - struct FTW ftw; - - /* Flags passed to `nftw' function. 0 for `ftw'. */ - int flags; - - /* Conversion array for flag values. It is the identity mapping for - `nftw' calls, otherwise it maps the values to those known by - `ftw'. */ - const int *cvt_arr; - - /* Callback function. We always use the `nftw' form. */ - NFTW_FUNC_T func; - - /* Device of starting point. Needed for FTW_MOUNT. */ - dev_t dev; - - /* Data structure for keeping fingerprints of already processed - object. This is needed when not using FTW_PHYS. */ - void *known_objects; -}; - -static bool -ftw_allocate (struct ftw_data *data, size_t newsize) -{ - void *newp = realloc (data->dirstreams, data->maxdir - * sizeof (struct dir_data *) - + newsize); - if (newp == NULL) - return false; - data->dirstreams = newp; - data->dirbufsize = newsize; - data->dirbuf = (char *) data->dirstreams - + data->maxdir * sizeof (struct dir_data *); - return true; -} - -/* Internally we use the FTW_* constants used for `nftw'. When invoked - as `ftw', map each flag to the subset of values used by `ftw'. */ -static const int nftw_arr[] = -{ - FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN -}; - -static const int ftw_arr[] = -{ - FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS -}; - - -/* Forward declarations of local functions. */ -static int ftw_dir (struct ftw_data *data, struct STRUCT_STAT *st, - struct dir_data *old_dir); - - static int object_compare (const void *p1, const void *p2) { @@ -250,399 +67,37 @@ object_compare (const void *p1, const void *p2) return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev); } - static int -add_object (struct ftw_data *data, struct STRUCT_STAT *st) +add_object (void **known_objects, struct STRUCT_STAT *st) { struct known_object *newp = malloc (sizeof (struct known_object)); if (newp == NULL) return -1; newp->dev = st->st_dev; newp->ino = st->st_ino; - return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1; + return __tsearch (newp, known_objects, object_compare) ? 0 : -1; } static inline int -find_object (struct ftw_data *data, struct STRUCT_STAT *st) +find_object (void **known_objects, struct STRUCT_STAT *st) { struct known_object obj; obj.dev = st->st_dev; obj.ino = st->st_ino; - return __tfind (&obj, &data->known_objects, object_compare) != NULL; + return __tfind (&obj, known_objects, object_compare) != NULL; } - -static inline int -__attribute ((always_inline)) -open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp) +union func_callback_t { - int result = 0; - - if (data->dirstreams[data->actdir] != NULL) - { - /* Oh, oh. We must close this stream. Get all remaining - entries and store them as a list in the `content' member of - the `struct dir_data' variable. */ - size_t bufsize = 1024; - char *buf = malloc (bufsize); - - if (buf == NULL) - result = -1; - else - { - DIR *st = data->dirstreams[data->actdir]->stream; - struct dirent64 *d; - size_t actsize = 0; - - while ((d = __readdir64 (st)) != NULL) - { - size_t this_len = NAMLEN (d); - if (actsize + this_len + 2 >= bufsize) - { - char *newp; - bufsize += MAX (1024, 2 * this_len); - newp = (char *) realloc (buf, bufsize); - if (newp == NULL) - { - /* No more memory. */ - int save_err = errno; - free (buf); - __set_errno (save_err); - return -1; - } - buf = newp; - } - - *((char *) __mempcpy (buf + actsize, d->d_name, this_len)) - = '\0'; - actsize += this_len + 1; - } - - /* Terminate the list with an additional NUL byte. */ - buf[actsize++] = '\0'; - - /* Shrink the buffer to what we actually need. */ - void *content = realloc (buf, actsize); - data->dirstreams[data->actdir]->content = content; - if (content == NULL) - { - int save_err = errno; - free (buf); - __set_errno (save_err); - result = -1; - } - else - { - __closedir (st); - data->dirstreams[data->actdir]->stream = NULL; - data->dirstreams[data->actdir]->streamfd = -1; - data->dirstreams[data->actdir] = NULL; - } - } - } - - /* Open the new stream. */ - if (result == 0) - { - assert (data->dirstreams[data->actdir] == NULL); - - if (dfdp != NULL && *dfdp != -1) - { - int fd = __openat64_nocancel (*dfdp, data->dirbuf + data->ftw.base, - O_RDONLY | O_DIRECTORY | O_NDELAY); - dirp->stream = NULL; - if (fd != -1 && (dirp->stream = __fdopendir (fd)) == NULL) - __close_nocancel_nostatus (fd); - } - else - { - const char *name; - - if (data->flags & FTW_CHDIR) - { - name = data->dirbuf + data->ftw.base; - if (name[0] == '\0') - name = "."; - } - else - name = data->dirbuf; - - dirp->stream = __opendir (name); - } - - if (dirp->stream == NULL) - result = -1; - else - { - dirp->streamfd = __dirfd (dirp->stream); - dirp->content = NULL; - data->dirstreams[data->actdir] = dirp; - - if (++data->actdir == data->maxdir) - data->actdir = 0; - } - } - - return result; -} - + FTW_FUNC_T ftw_func; + NFTW_FUNC_T nftw_func; +}; static int -process_entry (struct ftw_data *data, struct dir_data *dir, const char *name, - size_t namlen, int d_type) +ftw_startup (const char *dir, bool is_nftw, union func_callback_t func, + int descriptors, int flags) { - struct STRUCT_STAT st; - int result = 0; - int flag = 0; - size_t new_buflen; - - if (name[0] == '.' && (name[1] == '\0' - || (name[1] == '.' && name[2] == '\0'))) - /* Don't process the "." and ".." entries. */ - return 0; - - new_buflen = data->ftw.base + namlen + 2; - if (data->dirbufsize < new_buflen - && !ftw_allocate (data, 2 * new_buflen)) - return -1; - - *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\0'; - - int statres; - if (dir->streamfd != -1) - statres = FSTATAT (dir->streamfd, name, &st, - (data->flags & FTW_PHYS) ? AT_SYMLINK_NOFOLLOW : 0); - else - { - if ((data->flags & FTW_CHDIR) == 0) - name = data->dirbuf; - - statres = ((data->flags & FTW_PHYS) - ? LSTAT (name, &st) - : STAT (name, &st)); - } - - if (statres < 0) - { - if (errno != EACCES && errno != ENOENT) - result = -1; - else if (data->flags & FTW_PHYS) - flag = FTW_NS; - else - { - /* Old code left ST undefined for dangling DT_LNK without - FTW_PHYS set; a clarification at the POSIX level suggests - it should contain information about the link (ala lstat). - We do our best to fill in what data we can. */ - if (dir->streamfd != -1) - statres = FSTATAT (dir->streamfd, name, &st, - AT_SYMLINK_NOFOLLOW); - else - statres = LSTAT (name, &st); - if (statres == 0 && S_ISLNK (st.st_mode)) - flag = FTW_SLN; - else - flag = FTW_NS; - } - } - else - { - if (S_ISDIR (st.st_mode)) - flag = FTW_D; - else if (S_ISLNK (st.st_mode)) - flag = FTW_SL; - else - flag = FTW_F; - } - - if (result == 0 - && (flag == FTW_NS - || !(data->flags & FTW_MOUNT) || st.st_dev == data->dev)) - { - if (flag == FTW_D) - { - if ((data->flags & FTW_PHYS) - || (!find_object (data, &st) - /* Remember the object. */ - && (result = add_object (data, &st)) == 0)) - result = ftw_dir (data, &st, dir); - } - else - result = (*data->func) (data->dirbuf, &st, data->cvt_arr[flag], - &data->ftw); - } - - if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE) - result = 0; - - return result; -} - - -static int -__attribute ((noinline)) -ftw_dir (struct ftw_data *data, struct STRUCT_STAT *st, struct dir_data *old_dir) -{ - struct dir_data dir; - struct dirent64 *d; - int previous_base = data->ftw.base; - int result; - char *startp; - - /* Open the stream for this directory. This might require that - another stream has to be closed. */ - result = open_dir_stream (old_dir == NULL ? NULL : &old_dir->streamfd, - data, &dir); - if (result != 0) - { - if (errno == EACCES) - /* We cannot read the directory. Signal this with a special flag. */ - result = (*data->func) (data->dirbuf, st, FTW_DNR, &data->ftw); - - return result; - } - - /* First, report the directory (if not depth-first). */ - if (!(data->flags & FTW_DEPTH)) - { - result = (*data->func) (data->dirbuf, st, FTW_D, &data->ftw); - if (result != 0) - { - int save_err; -fail: - save_err = errno; - __closedir (dir.stream); - dir.streamfd = -1; - __set_errno (save_err); - - if (data->actdir-- == 0) - data->actdir = data->maxdir - 1; - data->dirstreams[data->actdir] = NULL; - return result; - } - } - - /* If necessary, change to this directory. */ - if (data->flags & FTW_CHDIR) - { - if (__fchdir (__dirfd (dir.stream)) < 0) - { - result = -1; - goto fail; - } - } - - /* Next, update the `struct FTW' information. */ - ++data->ftw.level; - startp = strchr (data->dirbuf, '\0'); - /* There always must be a directory name. */ - assert (startp != data->dirbuf); - if (startp[-1] != '/') - *startp++ = '/'; - data->ftw.base = startp - data->dirbuf; - - while (dir.stream != NULL && (d = __readdir64 (dir.stream)) != NULL) - { - int d_type = DT_UNKNOWN; -#ifdef _DIRENT_HAVE_D_TYPE - d_type = d->d_type; -#endif - result = process_entry (data, &dir, d->d_name, NAMLEN (d), d_type); - if (result != 0) - break; - } - - if (dir.stream != NULL) - { - /* The stream is still open. I.e., we did not need more - descriptors. Simply close the stream now. */ - int save_err = errno; - - assert (dir.content == NULL); - - __closedir (dir.stream); - dir.streamfd = -1; - __set_errno (save_err); - - if (data->actdir-- == 0) - data->actdir = data->maxdir - 1; - data->dirstreams[data->actdir] = NULL; - } - else - { - int save_err; - char *runp = dir.content; - - while (result == 0 && *runp != '\0') - { - char *endp = strchr (runp, '\0'); - - // XXX Should store the d_type values as well?! - result = process_entry (data, &dir, runp, endp - runp, DT_UNKNOWN); - - runp = endp + 1; - } - - save_err = errno; - free (dir.content); - __set_errno (save_err); - } - - if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS) - result = 0; - - /* Prepare the return, revert the `struct FTW' information. */ - data->dirbuf[data->ftw.base - 1] = '\0'; - --data->ftw.level; - data->ftw.base = previous_base; - - /* Finally, if we process depth-first report the directory. */ - if (result == 0 && (data->flags & FTW_DEPTH)) - result = (*data->func) (data->dirbuf, st, FTW_DP, &data->ftw); - - if (old_dir - && (data->flags & FTW_CHDIR) - && (result == 0 - || ((data->flags & FTW_ACTIONRETVAL) - && (result != -1 && result != FTW_STOP)))) - { - /* Change back to the parent directory. */ - int done = 0; - if (old_dir->stream != NULL) - if (__fchdir (__dirfd (old_dir->stream)) == 0) - done = 1; - - if (!done) - { - if (data->ftw.base == 1) - { - if (__chdir ("/") < 0) - result = -1; - } - else - if (__chdir ("..") < 0) - result = -1; - } - } - - return result; -} - - -static int -__attribute ((noinline)) -ftw_startup (const char *dir, int is_nftw, void *func, int descriptors, - int flags) -{ - struct ftw_data data = { .dirstreams = NULL }; - struct STRUCT_STAT st; - int result = 0; - int save_err; - int cwdfd = -1; - char *cwd = NULL; - char *cp; - /* First make sure the parameters are reasonable. */ if (dir[0] == '\0') { @@ -650,181 +105,310 @@ ftw_startup (const char *dir, int is_nftw, void *func, int descriptors, return -1; } - data.maxdir = descriptors < 1 ? 1 : descriptors; - data.actdir = 0; - /* PATH_MAX is always defined when we get here. */ - if (!ftw_allocate (&data, MAX (2 * strlen (dir), PATH_MAX))) + /* Data structure for keeping fingerprints of already processed + object. This is needed when not using FTW_PHYS. */ + void *known_objects = NULL; + /* Device of starting point. Needed for FTW_MOUNT. */ + dev_t root_dev = 0; + + /* NB: The fts FTS_LOGICAL implies on FTS_NOCHDIR, so to to proper implement + FTW_CHDIR it requires manually manage the chdir / fchdir dance around + the user's callback. The BUF is used to create the required path. */ + struct scratch_buffer buf; + scratch_buffer_init (&buf); + + int start_fd = __open (".", O_RDONLY | O_CLOEXEC); + if (start_fd < -1) return -1; - memset (data.dirstreams, '\0', data.maxdir * sizeof (struct dir_data *)); - cp = __stpcpy (data.dirbuf, dir); - /* Strip trailing slashes. */ - while (cp > data.dirbuf + 1 && cp[-1] == '/') - --cp; - *cp = '\0'; - data.ftw.level = 0; + int fts_options = 0; + if (flags & FTW_PHYS) + fts_options |= FTS_PHYSICAL; + else + fts_options |= FTS_LOGICAL; - /* Find basename. */ - while (cp > data.dirbuf && cp[-1] != '/') - --cp; - data.ftw.base = cp - data.dirbuf; + if (flags & FTW_MOUNT) + fts_options |= FTS_XDEV; - data.flags = flags; + if (!(flags & FTW_CHDIR)) + fts_options |= FTS_NOCHDIR; - /* This assignment might seem to be strange but it is what we want. - The trick is that the first three arguments to the `ftw' and - `nftw' callback functions are equal. Therefore we can call in - every case the callback using the format of the `nftw' version - and get the correct result since the stack layout for a function - call in C allows this. */ - data.func = (NFTW_FUNC_T) func; + char *const paths[] = { (char *)dir, NULL }; - /* Since we internally use the complete set of FTW_* values we need - to reduce the value range before calling a `ftw' callback. */ - data.cvt_arr = is_nftw ? nftw_arr : ftw_arr; - - /* No object known so far. */ - data.known_objects = NULL; - - /* Now go to the directory containing the initial file/directory. */ - if (flags & FTW_CHDIR) + /* NB: ingnore 'descriptors' limits since fts manages fds dynamically. */ + FTS_TYPE *ftsp = FTS_OPEN (paths, fts_options, NULL); + if (!ftsp) { - /* We have to be able to go back to the current working - directory. The best way to do this is to use a file - descriptor. */ - cwdfd = __open (".", O_RDONLY | O_DIRECTORY); - if (cwdfd == -1) - { - /* Try getting the directory name. This can be needed if - the current directory is executable but not readable. */ - if (errno == EACCES) - /* GNU extension ahead. */ - cwd = __getcwd (NULL, 0); - - if (cwd == NULL) - goto out_fail; - } - else if (data.maxdir > 1) - /* Account for the file descriptor we use here. */ - --data.maxdir; - - if (data.ftw.base > 0) - { - /* Change to the directory the file is in. In data.dirbuf - we have a writable copy of the file name. Just NUL - terminate it for now and change the directory. */ - if (data.ftw.base == 1) - /* I.e., the file is in the root directory. */ - result = __chdir ("/"); - else - { - char ch = data.dirbuf[data.ftw.base - 1]; - data.dirbuf[data.ftw.base - 1] = '\0'; - result = __chdir (data.dirbuf); - data.dirbuf[data.ftw.base - 1] = ch; - } - } + __close (start_fd); + return -1; } - /* Get stat info for start directory. */ - if (result == 0) + FTSENT_TYPE *ent = NULL; + int rc = 0; + int save_err; + + bool postorder = (flags & FTW_DEPTH) != 0; + + /* Used to proper support FTW_SKIP_SIBLINGS to avoid call fts_read again + of the next iteration. */ + bool skip_read = false; + + while (true) { - const char *name; + if (!skip_read) + { + errno = 0; + ent = FTS_READ (ftsp); + } + skip_read = false; - if (data.flags & FTW_CHDIR) + if (ent == NULL) { - name = data.dirbuf + data.ftw.base; - if (name[0] == '\0') - name = "."; - } - else - name = data.dirbuf; + if (errno != 0) + goto done; + break; + } - if (((flags & FTW_PHYS) - ? LSTAT (name, &st) - : STAT (name, &st)) < 0) + if (ent->fts_level == FTS_ROOTLEVEL) { - if (!(flags & FTW_PHYS) - && errno == ENOENT - && LSTAT (name, &st) == 0 - && S_ISLNK (st.st_mode)) - result = (*data.func) (data.dirbuf, &st, data.cvt_arr[FTW_SLN], - &data.ftw); - else - /* No need to call the callback since we cannot say anything - about the object. */ - result = -1; - } - else - { - if (S_ISDIR (st.st_mode)) + /* If the STARTING path cannot be accessed, nftw must fail rather + than calling the callback with FTW_NS (it is required only for + FTS_ROOTLEVEL). */ + if (ent->fts_info == FTS_NS && ent->fts_errno != 0) { - /* Remember the device of the initial directory in case - FTW_MOUNT is given. */ - data.dev = st.st_dev; + rc = -1; + errno = ent->fts_errno; + goto done; + } - /* We know this directory now. */ - if (!(flags & FTW_PHYS)) - result = add_object (&data, &st); + /* Remember the device of the initial directory in case FTW_MOUNT. */ + if (ent->fts_statp) + root_dev = ent->fts_statp->st_dev; + } - if (result == 0) - result = ftw_dir (&data, &st, NULL); + /* Handle FTW_MOUNT. */ + bool is_mount_crossing = false; + if ((flags & FTW_MOUNT) && ent->fts_level > FTS_ROOTLEVEL + && ent->fts_statp) + if (ent->fts_statp->st_dev != root_dev) + is_mount_crossing = true; + + int fn_flag; + switch (ent->fts_info) + { + case FTS_D: + /* Remember the device of the initial directory in case FTW_MOUNT + is given. */ + if (ent->fts_level == FTS_ROOTLEVEL) + { + if (!(flags & FTW_PHYS) + && add_object (&known_objects, ent->fts_statp) == -1) + goto done; } else { - int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F; - - result = (*data.func) (data.dirbuf, &st, data.cvt_arr[flag], - &data.ftw); + if (!(flags & FTW_PHYS) + && find_object (&known_objects, ent->fts_statp)) + continue; + if (add_object (&known_objects, ent->fts_statp) == -1) + goto done; } - } - if ((flags & FTW_ACTIONRETVAL) - && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS)) - result = 0; + /* Normally we skip FTS_D in depth mode, but for a mount + crossing, fts (FTS_XDEV) will NOT descend. We will never + get FTS_DP. We must handle it manually. */ + if (postorder && !is_mount_crossing) + continue; + + /* fts returns FTS_D for directories it has not tried to open yet, + even if it has not read permissions. The nftw must report + FTW_DNR instead. */ + if (__faccessat (start_fd, ent->fts_accpath, R_OK, AT_EACCESS) == 0) + fn_flag = FTW_D; + else + { + fn_flag = FTW_DNR; + FTS_SET (ftsp, ent, FTS_SKIP); + } + break; + + case FTS_DP: + if (!postorder) + continue; + fn_flag = FTW_DP; + break; + + case FTS_DEFAULT: + case FTS_F: + fn_flag = FTW_F; + break; + + case FTS_SL: + fn_flag = FTW_SL; + break; + + case FTS_SLNONE: + fn_flag = FTW_SLN; + break; + + case FTS_DNR: + fn_flag = FTW_DNR; + break; + + case FTS_NS: + fn_flag = FTW_NS; + break; + + case FTS_DC: + if (S_ISDIR (ent->fts_statp->st_mode)) + { + if (!(flags & FTW_PHYS) + && find_object (&known_objects, ent->fts_statp)) + continue; + if (add_object (&known_objects, ent->fts_statp) == -1) + goto done; + } + /* A directory cycle was detected (Logical walk only) and + instead of aborting with ELOOP, we report this as a + symlink that cannot be successfully followed (FTW_SLN). */ + fn_flag = FTW_SLN; + break; + + default: + rc = -1; + goto done; + } + + struct FTW ftw_data = { + .base = ent->fts_pathlen - ent->fts_namelen, + .level = ent->fts_level + }; + + bool chdir_performed = false; + if (flags & FTW_CHDIR) + { + const char *target_dir = NULL; + + size_t dir_len = ent->fts_pathlen - ent->fts_namelen; + if (dir_len == 0) + /* Case: "filename" (Root level file/dir with no path prefix): + the containing directory is the start directory. */ + target_dir = "."; + else + { + /* Case: "path/to/filename": we need to extract "path/to" */ + if (!scratch_buffer_set_array_size (&buf, dir_len, 1)) + goto done; + memcpy (buf.data, ent->fts_accpath, dir_len); + char *target = buf.data; + if (dir_len > 1 && target[dir_len-1] == '/') + target[dir_len-1] = '\0'; + target_dir = target; + } + + if (__chdir (target_dir) == 0) + chdir_performed = true; + else + { + rc = -1; + goto done; + } + } + + rc = is_nftw + ? func.nftw_func (ent->fts_path, ent->fts_statp, fn_flag, &ftw_data) + : func.ftw_func (ent->fts_path, ent->fts_statp, fn_flag); + + if (chdir_performed && __fchdir (start_fd) != 0) + { + rc = -1; + goto done; + } + + if (!(flags & FTW_ACTIONRETVAL)) + { + if (rc != 0) + break; + continue; + } + /* FTW_ACTIONRETVAL support. */ + switch (rc) + { + case FTW_CONTINUE: + /* Default behavior: just proceed to next entry. */ + break; + + case FTW_STOP: + /* Stop traversal immediately (success return code). We set rc=0 + here because FTW_STOP is considered a "successful" stop, + unlike a non-zero return in standard mode. */ + rc = 0; + goto done; + + case FTW_SKIP_SUBTREE: + /* Only meaningful if we are currently visiting a directory in + pre-order (FTS_D). */ + if (ent->fts_info == FTS_D) + FTS_SET (ftsp, ent, FTS_SKIP); + break; + + case FTW_SKIP_SIBLINGS: + /* We must skip everything until we emerge at a lower level + (parent). */ + { + int current_level = ent->fts_level; + + /* Drain fts until level < current. */ + while ((ent = FTS_READ (ftsp)) != NULL) + { + if (ent->fts_level < current_level) + { + skip_read = true; + break; + } + } + + if (ent == NULL) + goto done; + } break; + + default: + rc = 0; + goto done; + } } - /* Return to the start directory (if necessary). */ - if (cwdfd != -1) - { - int save_err = errno; - __fchdir (cwdfd); - __close_nocancel_nostatus (cwdfd); - __set_errno (save_err); - } - else if (cwd != NULL) - { - int save_err = errno; - __chdir (cwd); - free (cwd); - __set_errno (save_err); - } - - /* Free all memory. */ - out_fail: +done: + scratch_buffer_free (&buf); + __tdestroy (known_objects, free); save_err = errno; - __tdestroy (data.known_objects, free); - free (data.dirstreams); + FTS_CLOSE (ftsp); + __close (start_fd); __set_errno (save_err); - return result; + return rc; } - - /* Entry points. */ int FTW_NAME (const char *path, FTW_FUNC_T func, int descriptors) { - return ftw_startup (path, 0, func, descriptors, 0); + return ftw_startup (path, + false, + (union func_callback_t) { .ftw_func = func }, + descriptors, + 0); } #ifndef NFTW_OLD_NAME int NFTW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags) { - return ftw_startup (path, 1, func, descriptors, flags); + return ftw_startup (path, true, + (union func_callback_t) { .nftw_func = func }, + descriptors, + flags); } #else @@ -841,7 +425,11 @@ NFTW_NEW_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags) __set_errno (EINVAL); return -1; } - return ftw_startup (path, 1, func, descriptors, flags); + return ftw_startup (path, + true, + (union func_callback_t) { .nftw_func = func }, + descriptors, + flags); } versioned_symbol (libc, NFTW_NEW_NAME, NFTW_NAME, GLIBC_2_3_3); @@ -856,7 +444,11 @@ attribute_compat_text_section NFTW_OLD_NAME (const char *path, NFTW_FUNC_T func, int descriptors, int flags) { flags &= (FTW_PHYS | FTW_MOUNT | FTW_CHDIR | FTW_DEPTH); - return ftw_startup (path, 1, func, descriptors, flags); + return ftw_startup (path, + true, + (union func_callback_t) { .nftw_func = func }, + descriptors, + flags); } compat_symbol (libc, NFTW_OLD_NAME, NFTW_NAME, GLIBC_2_1); diff --git a/io/ftw64-time64.c b/io/ftw64-time64.c index 2df871f802..517dc93d92 100644 --- a/io/ftw64-time64.c +++ b/io/ftw64-time64.c @@ -23,11 +23,15 @@ # define NFTW_NAME __nftw64_time64 # define INO_T ino64_t # define STRUCT_STAT __stat64_t64 -# define LSTAT __lstat64_time64 -# define STAT __stat64_time64 -# define FSTATAT __fstatat64_time64 # define FTW_FUNC_T __ftw64_time64_func_t # define NFTW_FUNC_T __nftw64_time64_func_t +# define FTS_TYPE FTS64_TIME64 +# define FTSENT_TYPE FSTENT64_TIME64 +# define FTS_OPEN __fts64_open_time64 +# define FTS_READ __fts64_read_time64 +# define FTS_SET __fts64_set_time64 +# define FTS_CLOSE __fts64_close_time64 + # include "ftw.c" #endif diff --git a/io/ftw64.c b/io/ftw64.c index 0d7cb30091..b61b0e24b8 100644 --- a/io/ftw64.c +++ b/io/ftw64.c @@ -22,10 +22,14 @@ #define NFTW_NEW_NAME __new_nftw64 #define INO_T ino64_t #define STRUCT_STAT stat64 -#define LSTAT __lstat64 -#define STAT __stat64 -#define FSTATAT __fstatat64 #define FTW_FUNC_T __ftw64_func_t #define NFTW_FUNC_T __nftw64_func_t +#define FTS_TYPE FTS64 +#define FTSENT_TYPE FTSENT64 +#define FTS_OPEN __fts64_open +#define FTS_READ __fts64_read +#define FTS_SET __fts64_set +#define FTS_CLOSE __fts64_close + #include "ftw.c" diff --git a/io/ftwtest-sh b/io/ftwtest-sh index 9758e18f0d..a8e5160210 100644 --- a/io/ftwtest-sh +++ b/io/ftwtest-sh @@ -62,6 +62,8 @@ ln -s $tmpdir/foo/lvl1/lvl2 $tmpdir/foo/lvl1/lvl2/link@2 ln -s $tmpdir/foo/lvl1/lvl2/lvl3/lvl4 $tmpdir/foo/lvl1/link@1 echo > $tmpdir/bar/xo chmod a-x,a+r $tmpdir/bar +mkdir $tmpdir/ndir +chmod a-r $tmpdir/ndir testout=$(mktemp $tmp/ftwtest-tmp-XXXXXX.out) @@ -73,6 +75,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0 base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1 base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1 base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1 +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1 base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_NS, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2 base = "$tmp/$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, level = 3 @@ -92,6 +95,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_DP, level = 0 base = "$tmp/$ftwtest/", file = "bar", flag = FTW_DP, level = 1 base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1 base = "$tmp/$ftwtest/", file = "foo", flag = FTW_DP, level = 1 +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1 base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_NS, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_DP, level = 2 base = "$tmp/$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, level = 3 @@ -111,6 +115,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0 base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1 base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1 base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1 +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1 base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_NS, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2 base = "$tmp/$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, level = 3 @@ -125,7 +130,9 @@ EOF rm $testout # For the next test everything must be readable. +chmod a+r $tmpdir/ndir chmod -fR a+x $tmpdir +chmod a-r $tmpdir/ndir $testprogram --chdir $tmpdir | sort > $testout @@ -138,6 +145,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, cwd = $tmpreal, level = 0 base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1 base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, cwd = $tmpreal/$ftwtest, level = 1 base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1 +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, cwd = $tmpreal/$ftwtest, level = 1 base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_F, cwd = $tmpreal/$ftwtest/bar, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, cwd = $tmpreal/$ftwtest/foo, level = 2 base = "$tmp/$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, cwd = $tmpreal/$ftwtest/foo/lvl1, level = 3 @@ -160,6 +168,7 @@ base = "", file = "$ftwtest", flag = FTW_D, cwd = $tmpreal, level = 0 base = "$ftwtest/", file = "bar", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1 base = "$ftwtest/", file = "baz", flag = FTW_F, cwd = $tmpreal/$ftwtest, level = 1 base = "$ftwtest/", file = "foo", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1 +base = "$ftwtest/", file = "ndir", flag = FTW_DNR, cwd = $tmpreal/$ftwtest, level = 1 base = "$ftwtest/bar/", file = "xo", flag = FTW_F, cwd = $tmpreal/$ftwtest/bar, level = 2 base = "$ftwtest/foo/", file = "lvl1", flag = FTW_D, cwd = $tmpreal/$ftwtest/foo, level = 2 base = "$ftwtest/foo/lvl1/", file = "file@1", flag = FTW_F, cwd = $tmpreal/$ftwtest/foo/lvl1, level = 3 @@ -182,6 +191,7 @@ base = "$ftwtest/", file = ".", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 0 base = "$ftwtest/./", file = "bar", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1 base = "$ftwtest/./", file = "baz", flag = FTW_F, cwd = $tmpreal/$ftwtest, level = 1 base = "$ftwtest/./", file = "foo", flag = FTW_D, cwd = $tmpreal/$ftwtest, level = 1 +base = "$ftwtest/./", file = "ndir", flag = FTW_DNR, cwd = $tmpreal/$ftwtest, level = 1 base = "$ftwtest/./bar/", file = "xo", flag = FTW_F, cwd = $tmpreal/$ftwtest/bar, level = 2 base = "$ftwtest/./foo/", file = "lvl1", flag = FTW_D, cwd = $tmpreal/$ftwtest/foo, level = 2 base = "$ftwtest/./foo/lvl1/", file = "file@1", flag = FTW_F, cwd = $tmpreal/$ftwtest/foo/lvl1, level = 3 @@ -226,6 +236,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0 base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1 base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1 base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1 +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1 base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_F, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1b", flag = FTW_D, level = 2 @@ -250,6 +261,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0 base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1 base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1 base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1 +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1 base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_F, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1b", flag = FTW_D, level = 2 @@ -275,6 +287,7 @@ base = "$tmp/", file = "$ftwtest", flag = FTW_D, level = 0 base = "$tmp/$ftwtest/", file = "bar", flag = FTW_D, level = 1 base = "$tmp/$ftwtest/", file = "baz", flag = FTW_F, level = 1 base = "$tmp/$ftwtest/", file = "foo", flag = FTW_D, level = 1 +base = "$tmp/$ftwtest/", file = "ndir", flag = FTW_DNR, level = 1 base = "$tmp/$ftwtest/bar/", file = "xo", flag = FTW_F, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1", flag = FTW_D, level = 2 base = "$tmp/$ftwtest/foo/", file = "lvl1b", flag = FTW_D, level = 2 diff --git a/io/tst-nftw-bz33882.c b/io/tst-nftw-bz33882.c new file mode 100644 index 0000000000..d2a7b6a678 --- /dev/null +++ b/io/tst-nftw-bz33882.c @@ -0,0 +1,88 @@ +/* Check if nested directory level does not overflow the stack (BZ #33882) + Copyright (C) 2026 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Typical stack frame for a recursive function is 64–256 bytes, with a nested + depth of 5000 would required around 640Kb of stack space. */ +enum { nested_depth = 5000 }; +enum { stack_limit_kb = 512 }; + +/* Short name to maximize depth/path ratio. */ +static const char dir_name[] = "d"; + +static void +do_cleanup (void) +{ + xchdir (".."); + for (int i = 0; i < nested_depth; i++) + { + remove (dir_name); + xchdir (".."); + } + remove (dir_name); +} +#define CLEANUP_HANDLER do_cleanup + +static int +my_func (const char *file, const struct stat *sb, int flag, struct FTW *ftwbuf) +{ + return 0; +} + +/* Set the RLIMIT_AS limit to the value in *LIMIT. */ +static void +xsetrlimit_stack (const struct rlimit *limit) +{ + if (setrlimit (RLIMIT_STACK, limit) != 0) + FAIL_EXIT1 ("setrlimit (RLIMIT_STACK, %lu): %m", + (unsigned long) limit->rlim_cur); +} + +static int +do_test (void) +{ + xsetrlimit_stack (&(struct rlimit) { .rlim_cur = stack_limit_kb * 1024, + .rlim_max = stack_limit_kb * 1024 }); + + char *tempdir = support_create_temp_directory ("tst-bz33882"); + + xchdir (tempdir); + for (int i = 0; i < nested_depth; i++) + { + xmkdir (dir_name, 0700); + xchdir (dir_name); + } + + TEST_COMPARE (nftw (tempdir, my_func, 20, 0), 0); + + free (tempdir); + + do_cleanup (); + + return 0; +} + +#include diff --git a/sysdeps/wordsize-64/fts.c b/sysdeps/wordsize-64/fts.c index 159dc1febe..b10d81714a 100644 --- a/sysdeps/wordsize-64/fts.c +++ b/sysdeps/wordsize-64/fts.c @@ -12,8 +12,8 @@ #undef fts64_set #undef fts64_children -weak_alias (fts_open, fts64_open) -weak_alias (fts_close, fts64_close) -weak_alias (fts_read, fts64_read) -weak_alias (fts_set, fts64_set) -weak_alias (fts_children, fts64_children) +weak_alias (__fts_open, fts64_open) +weak_alias (__fts_close, fts64_close) +weak_alias (__fts_read, fts64_read) +weak_alias (__fts_set, fts64_set) +weak_alias (__fts_children, fts64_children)