ftw(3)

✨ ftw, nftw β€” file tree walk ✨

okay bestie, sit down, hydrate, because we're about to walk an entire directory tree together and it's gonna be so cute πŸ’– this is a manual page (ftw(3)) from the Library Functions Manual, and it lives in the Standard C library (libc, linked with -lc). two functions, one vibe: ftw() and nftw(). let's get into it.


🌸 SYNOPSIS 🌸

#include <ftw.h>

int nftw(const char *dirpath,
        typeof(int (const char *fpath, const struct stat *sb,
                    int typeflag, struct FTW *ftwbuf))
            *fn,
        int nopenfd, int flags);

[[deprecated]]
int ftw(const char *dirpath,
        typeof(int (const char *fpath, const struct stat *sb,
                    int typeflag))
            *fn,
        int nopenfd);

πŸŽ€ Feature Test Macro Requirements for glibc (peep feature_test_macros(7)) πŸŽ€

nftw():
    _XOPEN_SOURCE >= 500

like, if you want nftw() you gotta define that macro first, no cap. πŸ™ˆ


πŸ’… DESCRIPTION πŸ’…

nftw() walks through the entire directory tree sitting under the directory dirpath, and it calls fn() once for every single entry in that tree. by default, directories get handled before the files and subdirectories they contain β€” that's a preorder traversal, and honestly? iconic behaviour.

now, to avoid absolutely devouring all of the calling process's file descriptors (we been knew, fd's are precious), nopenfd tells you the maximum number of directories that nftw() will hold open at the same time. when the search depth goes past that number, nftw() gets slower because directories have to be closed and then reopened. nftw() uses at most one file descriptor for each level in the directory tree. so like, it's not greedy. it's mindful. main character energy.

for each entry found in the tree, nftw() calls fn() with four arguments: fpath, sb, typeflag, and ftwbuf.

  • fpath is the pathname of the entry. it's expressed either as a pathname relative to the calling process's current working directory at the time of the call to nftw() (if dirpath was given as a relative pathname), or as an absolute pathname (if dirpath was given as an absolute pathname).
  • sb is a pointer to the stat structure returned by a call to stat(2) for fpath. 🫢

the typeflag argument passed to fn() is an integer with one of these values:

  • FTW_F β€” fpath is a regular file. classic.
  • FTW_D β€” fpath is a directory. periodt.
  • FTW_DNR β€” fpath is a directory that can't be read. 😭
  • FTW_DP β€” fpath is a directory, and FTW_DEPTH was specified in flags. (heads up: if FTW_DEPTH was not specified in flags, then directories will always be visited with typeflag set to FTW_D.) all of the files and subdirectories within fpath have been processed. so it's the after-party version of FTW_D.
  • FTW_NS β€” the stat(2) call failed on fpath, which is not a symbolic link. the probable cause? the caller had read permission on the parent directory, so the filename fpath could be seen, but did not have execute permission, so the file couldn't be reached for stat(2). in this case, the contents of the buffer pointed to by sb are undefined. πŸ’€ do not read that buffer, bestie, it's giving chaos.
  • FTW_SL β€” fpath is a symbolic link, and FTW_PHYS was set in flags.
  • FTW_SLN β€” fpath is a symbolic link pointing to a nonexistent file. (this only happens if FTW_PHYS is not set.) when it does, the sb argument passed to fn() contains the information returned by performing lstat(2) on the "dangling" symbolic link. (but like, see BUGS below. πŸ™ˆ)

the fourth argument (ftwbuf) that nftw() hands over when calling fn() is a pointer to a structure of type FTW:

struct FTW {
    int base;
    int level;
};
  • base is the offset of the filename (i.e., the basename component) in the pathname given in fpath.
  • level is the depth of fpath in the directory tree, relative to the root of the tree (dirpath, which has depth 0).

to stop the tree walk, fn() returns a nonzero value, and that value becomes the return value of nftw(). as long as fn() returns 0, nftw() keeps going β€” either until it's traversed the entire tree, in which case it returns zero, or until it hits an error (like a malloc(3) failure), in which case it returns -1.

⚠️ real talk: because nftw() uses dynamic data structures, the only safe way to exit out of a tree walk is to return a nonzero value from fn(). if you want a signal to be able to terminate the walk without causing a memory leak, have the handler set a global flag that fn() checks. and do not use longjmp(3) unless the program is going to terminate. i'm serious. πŸ”₯

🌈 the flags argument 🌈

flags is formed by ORing together zero or more of these babes:

  • FTW_ACTIONRETVAL (since glibc 2.3.3) β€” if this glibc-specific flag is set, then nftw() handles the return value from fn() differently. fn() should then return one of these values:
    • FTW_CONTINUE β€” tells nftw() to continue normally. slay.
    • FTW_SKIP_SIBLINGS β€” if fn() returns this, then siblings of the current entry get skipped, and processing continues in the parent.
    • FTW_SKIP_SUBTREE β€” if fn() is called with an entry that's a directory (typeflag is FTW_D), this return value prevents objects within that directory from being passed as arguments to fn(). nftw() continues processing with the next sibling of the directory.
    • FTW_STOP β€” causes nftw() to return immediately, with the return value FTW_STOP.

other return values could get associated with new actions in the future, so fn() should not return values other than the ones listed above. and heads up: you need to define the feature test macro _GNU_SOURCE (before including any header files) in order to get the definition of FTW_ACTIONRETVAL from <ftw.h>. πŸ“Œ

  • FTW_CHDIR β€” if set, do a chdir(2) into each directory before handling its contents. this is useful if the program needs to do some action in the directory where fpath resides. (specifying this flag has no effect on the pathname that gets passed in the fpath argument of fn().)
  • FTW_DEPTH β€” if set, do a post-order traversal, that is, call fn() for the directory itself after handling the contents of the directory and its subdirectories. (by default, each directory is handled before its contents β€” so this flips the whole thing. πŸ™ƒ)
  • FTW_MOUNT β€” if set, stay within the same filesystem (i.e., do not cross mount points).
  • FTW_PHYS β€” if set, do not follow symbolic links. (this is what you want.) if not set, symbolic links are followed, but no file gets reported twice. and if FTW_PHYS is not set but FTW_DEPTH is set, then the function fn() is never called for a directory that would be a descendant of itself. πŸŒ€

πŸ¦‹ ftw() πŸ¦‹

ftw() is the older function, and it offers a subset of what nftw() can do. the notable differences are:

  • ftw() has no flags argument. it behaves the same as when nftw() is called with flags specified as zero.
  • the callback function, fn(), is not supplied with a fourth argument.
  • the range of values passed via the typeflag argument to fn() is smaller: just FTW_F, FTW_D, FTW_DNR, FTW_NS, and (possibly) FTW_SL.

πŸ‘‘ RETURN VALUE πŸ‘‘

these functions return 0 on success, and -1 if an error occurs.

if fn() returns nonzero, then the tree walk is terminated, and the value returned by fn() is returned as the result of ftw() or nftw().

if nftw() is called with the FTW_ACTIONRETVAL flag, then the only nonzero value that should be used by fn() to terminate the tree walk is FTW_STOP, and that value is returned as the result of nftw().


⭐ ATTRIBUTES ⭐

for an explanation of the terms used in this section, peep attributes(7). (the original is a lil table, but we're doing it as a list because tables are not the vibe here πŸ’…)

  • nftw() β€” Thread safety β€” MT-Safe cwd
  • ftw() β€” Thread safety β€” MT-Safe

🌸 VERSIONS 🌸

in some implementations (e.g., glibc), ftw() will never use FTW_SL. on other systems, FTW_SL occurs only for symbolic links that do not point to an existing file. and again, on other systems, ftw() will use FTW_SL for each symbolic link. πŸ˜΅β€πŸ’«

if fpath is a symbolic link and stat(2) failed, POSIX.1-2008 states that it is undefined whether FTW_NS or FTW_SL gets passed in typeflag. so like… for predictable results, use nftw(). fr fr.


πŸ’œ STANDARDS πŸ’œ

POSIX.1-2008.


πŸŽ€ HISTORY πŸŽ€

  • ftw() β€” POSIX.1-2001, SVr4, SUSv1. POSIX.1-2008 marks it as obsolete. rip boo, you had a good run. 🫑
  • nftw() β€” glibc 2.1. POSIX.1-2001, SUSv1.
  • FTW_SL β€” POSIX.1-2001, SUSv1.

πŸ“ NOTES πŸ“

POSIX.1-2008 notes that the results are unspecified if fn does not preserve the current working directory. so like, don't be out here wrecking the cwd, bestie. 😭


πŸ’€ BUGS πŸ’€

according to POSIX.1-2008, when the typeflag argument passed to fn() contains FTW_SLN, the buffer pointed to by sb should contain information about the dangling symbolic link (obtained by calling lstat(2) on the link).

early glibc versions correctly followed the POSIX specification on this point. however β€” as a result of a regression introduced in glibc 2.4, the contents of the buffer pointed to by sb were undefined when FTW_SLN is passed in typeflag. (more precisely, the contents of the buffer were left unchanged in this case.) πŸ™ˆ this regression was eventually fixed in glibc 2.30, so that the glibc implementation (once more) follows the POSIX specification. we love a redemption arc. ✨

✨ EXAMPLES ✨

okay bestie, demo time πŸ’… this lil program walks the whole directory tree under whatever path you hand it as the first command-line argument β€” and if you're feeling lazy and don't pass one at all? it just chills in the current directory instead 🫢 and while it's strolling around, it prints out all kinds of cute little facts about every file it bumps into.

oh and there's a second command-line argument too, bestie β€” that one's a string of characters that control what value gets assigned to the flags argument when nftw() gets called. character-driven flag selection, it's giving ✨configurable✨


πŸ’– Program source πŸ’–

okay here's the whole thing, copy it, compile it, slay it:

#define _XOPEN_SOURCE 500
#include <ftw.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int
display_info(const char *fpath, const struct stat *sb,
             int tflag, struct FTW *ftwbuf)
{
    printf("%-3s %2d ",
           (tflag == FTW_D) ?   "d"   : (tflag == FTW_DNR) ? "dnr" :
           (tflag == FTW_DP) ?  "dp"  : (tflag == FTW_F) ?   "f" :
           (tflag == FTW_NS) ?  "ns"  : (tflag == FTW_SL) ?  "sl" :
           (tflag == FTW_SLN) ? "sln" : "???",
           ftwbuf->level);

    if (tflag == FTW_NS)
        printf("-------");
    else
        printf("%7jd", (intmax_t) sb->st_size);

    printf("   %-40s %d %s\n",
           fpath, ftwbuf->base, fpath + ftwbuf->base);

    return 0;           /* To tell nftw() to continue */
}

int
main(int argc, char *argv[])
{
    int flags = 0;

    if (argc > 2 && strchr(argv[2], 'd') != NULL)
        flags |= FTW_DEPTH;
    if (argc > 2 && strchr(argv[2], 'p') != NULL)
        flags |= FTW_PHYS;

    if (nftw((argc < 2) ? "." : argv[1], display_info, 20, flags)
        == -1)
    {
        perror("nftw");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

no cap, the callback returning 0 is literally the "keep going bestie" signal β€” that's the whole /* To tell nftw() to continue */ moment πŸ₯Ή iconic.


🌈 SEE ALSO 🌈

  • stat(2) β€” the file-stat syscall lore πŸ’œ
  • fts(3) β€” the other tree-walker in the group chat πŸ‘‘
  • readdir(3) β€” for when you just wanna read one directory at a time πŸ™ˆ

and that's the whole page bestie, we did it πŸŽ€ this was straight from Linux man-pages 6.20, dated 2026-02-08 β€” go forth, walk your trees, and remember: return 0 to keep going, and always free your πŸ”₯main character energyπŸ”₯. periodt πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from
  • arch rolling: man-pages 6.20-1 Β· BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • πŸ“œ the page's license: GPL-2.0-or-later
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms