Linux - Traversing directories recursively with ftsopen() and ftsread()

The fts library can be used to traverse directories recursive.

  • fts_open (): Returns the FTS object for the directory passed as argument
  • fts_read (): Returns an FTSENT object for a file in an FTS object

Note that i am not familiar with fts apis, there can be something wrong.

Example : Traversing directories

The following example outputs all files for the "./" directory. The fts library traverses the folder recursively and finds all files.

The example was written referring to Android framework code.

main.cpp

#include <errno.h>
#include <string.h>
#include <fts.h>
#include <iostream>

using namespace std;

int main() {
    std::string path("./");

    FTS *fts;
    char *argv[] = { (char*) path.c_str(), nullptr };
    if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, nullptr))) {
        if (errno != ENOENT) {
            cout << "Failed to fts_open " << path;
        }
        return -1;
    }

    FTSENT *p;
    while ((p = fts_read(fts)) != nullptr) {
        switch (p->fts_info) {
            case FTS_DP:
                cout << "Directory: " << p->fts_path << "\n";
                break;
            case FTS_F:
                cout << "File: " << p->fts_path << "\n";
                break;
            case FTS_SL:
            case FTS_SLNONE:
                cout << "Symbolic link: " << p->fts_path << "\n";
                break;
        }
    }
    fts_close(fts);

    return 0;
}

Build & Run

Before running the code, the current directory state is:

$ tree
.
├── aa
│   └── file2.txt
├── bb
│   ├── dd
│   │   ├── file6.txt
│   │   └── file7.txt
│   ├── file3.txt
│   ├── file4.txt
│   └── file5.txt
├── cc
│   ├── file8.txt
│   ├── file9.txt
│   └── symbolic.txt -> file8.txt
├── file1.txt
├── main.cpp
└── main

Build and run with the following command:

$ g++ main.cpp -o main
$ ./main

In the results, all files under "./" were printed.

File: ./bb/file5.txt
File: ./bb/dd/file7.txt
File: ./bb/dd/file6.txt
Directory: ./bb/dd
File: ./bb/file3.txt
File: ./bb/file4.txt
Directory: ./bb
File: ./aa/file2.txt
Directory: ./aa
Symbolic link: ./cc/symbolic.txt
File: ./cc/file9.txt
File: ./cc/file8.txt
Directory: ./cc
File: ./file1.txt
File: ./main.cpp
File: ./main
Directory: ./

fts_open()

You can get the FTS object using fts_open() as follow. The file path is passed as an argument.

std::string path("./");
char *argv[] = { (char*) path.c_str(), nullptr };
if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, nullptr))) {
    if (errno != ENOENT) {
        cout << "Failed to fts_open " << path;
    }
    return -1;
}

The meanings of the options are as follows. See manpage for more details.

  • FTS_PHYSICAL : This option causes the fts routines to return FTSENT structures for symbolic links
  • FTS_NOCHDIR : To allow descending to arbitrary depths and improve performance
  • FTS_XDEV : This option prevents fts from descending into directories that have a different device number

fts_read()

You can traverse the directory using fts_read() as follow. You can get the file path with a the fts_path of the FTSENT object.

FTSENT *p;
while ((p = fts_read(fts)) != nullptr) {
    switch (p->fts_info) {
        case FTS_DP:
            cout << "Directory: " << p->fts_path << "\n";
            break;
        case FTS_F:
            cout << "File: " << p->fts_path << "\n";
            break;
        case FTS_SL:
        case FTS_SLNONE:
            cout << "Symbolic link: " << p->fts_path << "\n";
            break;
    }
}

The structure of FTSENT is as follows:

typedef struct _ftsent {
  int fts_info;		     /*	status for FTSENT structure */
  char *fts_accpath;		     /*	access path */
  char *fts_path;		     /*	root path */
  size_t fts_pathlen;	     /*	strlen(fts_path) */
  char *fts_name;		     /*	file name */
  size_t fts_namelen;	     /*	strlen(fts_name) */
  long fts_level;		     /*	depth (-1 to N)	*/
  int fts_errno;		     /*	file errno */
  long long fts_number;	     /*	local numeric value */
  void *fts_pointer;		     /*	local address value */
  struct ftsent *fts_parent;	     /*	parent directory */
  struct ftsent *fts_link;	     /*	next file structure */
  struct ftsent *fts_cycle;	     /*	cycle structure	*/
  struct stat *fts_statp;	     /*	stat(2)	information */
} FTSENT;

And the FTS file information used in the example above is as follows. See manpage for more details.

  • FTS_DP : A directory
  • FTS_F: A regular file
  • FTS_SL, FTS_SLNONE: A symbolic link

fts_close()

if you are done using the FTS ojbect, you have to close it as following.

fts_close(fts);

Reference

codechachaCopyright ©2019 codechacha