2

I have a question on fts(3). I am getting a segmentation fault whenever I try to access any members of the fts_children() function. When I read the man page at http://www.kernel.org/doc/man-pages/online/pages/man3/fts.3.html it claims to fill itself after the read function runs and returns a linked list linked through the link field in the structure. My suspicion is that the child_function is returning nothing but I feel like that doesn't line up with the man page. Am I supposed to be adding these files to the child buffer because I thought that was being done automatically? My code is below, Thanks!

#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fts.h>
#include<string.h>

int compare (const FTSENT**, const FTSENT**);

int main(int argc, char* const argv[])
{

        FTS* file_system = NULL;
        FTSENT* child = NULL;
        FTSENT* parent = NULL;
        FTSENT* temp = NULL;

        file_system = fts_open(argv + 1,FTS_COMFOLLOW | FTS_NOCHDIR,&compare);

        while( (parent = fts_read(file_system)) != NULL)
        {

             child = fts_children(file_system,0);
             printf("%s\n", child->fts_path);


        }
//      while (child ->fts_link != NULL)
      //         child = child->fts_link;
        fts_close(file_system);
        return 0;
}

int compare(const FTSENT** one, const FTSENT** two){
        return (strcmp((*one)->fts_name, (*two)->fts_name));
}
"test_fs.c" 43L, 1108C  
tpar44
  • 1,431
  • 4
  • 22
  • 35

2 Answers2

9

If you are only interested in traversing all directories and files for the specified path(s), just repeatedly call fts_read.

If you want to only iterate through the stream, @sehe's example could be rewritten as:

#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<fts.h>
#include<string.h>
#include<errno.h>

int compare (const FTSENT**, const FTSENT**);
void indent (int i);

int main(int argc, char* const argv[])
{
    FTS* file_system = NULL;
    FTSENT *node,    = NULL;

    if (argc<2)
    {
        printf("Usage: %s <path-spec>\n", argv[0]);
        exit(255);
    }

    file_system = fts_open(argv + 1,FTS_COMFOLLOW|FTS_NOCHDIR,&compare);

    if (NULL != file_system)
    {
        while( (node = fts_read(file_system)) != NULL)
        {
            switch (node->fts_info) 
            {
                case FTS_D :
                case FTS_F :
                case FTS_SL:
                    indent(node->fts_level);
                    printf("%s\n", node->fts_name);
                    break;
                default:
                    break;
            }
        }
        fts_close(file_system);
    }
    return 0;
}

int compare(const FTSENT** one, const FTSENT** two)
{
    return (strcmp((*one)->fts_name, (*two)->fts_name));
}

void indent(int i)
{
    for (; i > 0; i--) 
        printf("    ");
}

When you run it, it iterates through the stream and lists all files and directories in order:

★ mkdir -p test/A/1 test/A/2 test/B/1 test/B/2

★ tree test                                   
test
├── A
│   ├── 1
│   └── 2
└── B
    ├── 1
    └── 2

★ ./fts test                                  
test
    A
        1
        2
    B
        1
        2

Call fts_children only if you want a list of child nodes of a specific directory. In that case you must call fts_read at least once before calling fts_children; otherwise fts_children will only return nodes specified in argv parameter to fts_open.

Mr. Curious
  • 837
  • 9
  • 14
  • 1
    Upvoting because man fts does not explicitly state that subsequently calling fts_read will walk the filesystem. It implies it, but it is not at all obvious. – retrodev Feb 01 '16 at 14:18
  • Hi, is there any way to use fts in order to get the total number of regular files in root directory without traversing them first (which can take quite a while ...) – Zohar81 Apr 11 '18 at 10:52
  • Guess not. You'll have to call `fts_read()` once, then `fts_children()` and iterate through the linked list it returns and check if each child is a regular file or something else. – Mr. Curious Apr 16 '18 at 19:29
  • The man page states `Either FTS_LOGICAL or FTS_PHYSICAL must be provided to the fts_open() function.` http://man7.org/linux/man-pages/man3/fts.3.html – ishmael Sep 26 '19 at 13:31
  • @ishmael, I believe `FTS_COMFOLLOW` implies `FTS_LOGICAL`, at least per the [source](https://codebrowser.dev/glibc/glibc/io/fts.c.html#898): `if (ISSET(FTS_LOGICAL) || follow) { ...` – Mr. Curious Nov 24 '22 at 17:51
2

You simply need to add a NULL check.

You might want to

  • add one for file_system
  • check for command line arguments
  • Add more errorhandling:

    The fts_children() function returns a pointer to an FTSENT structure describing the first entry in a NULL terminated linked list of files in the directory, if successful. The fts_children() function may fail and set errno for any of the errors that the chdir(), malloc(), opendir(), readdir(), and stat() functions specify.

Update To the new question(s) in the comment:

  • The while loop for linked list traversal was misplaced (outside the outer loop?)
  • The printf displayed only the path... not the filename.

while you're at it:

#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fts.h>
#include<string.h>
#include<errno.h>

int compare (const FTSENT**, const FTSENT**);

int main(int argc, char* const argv[])
{
    FTS* file_system = NULL;
    FTSENT* child = NULL;
    FTSENT* parent = NULL;

    if (argc<2)
    {
        printf("Usage: %s <path-spec>\n", argv[0]);
        exit(255);
    }

    file_system = fts_open(argv + 1,FTS_COMFOLLOW | FTS_NOCHDIR,&compare);

    if (NULL != file_system)
    {
        while( (parent = fts_read(file_system)) != NULL)
        {
            child = fts_children(file_system,0);

            if (errno != 0)
            {
                perror("fts_children");
            }

            while ((NULL != child)
                && (NULL != child->fts_link))
            {
                child = child->fts_link;
                printf("%s%s\n", child->fts_path, child->fts_name);
            }
        }
        fts_close(file_system);
    }
    return 0;
}

int compare(const FTSENT** one, const FTSENT** two)
{
    return (strcmp((*one)->fts_name, (*two)->fts_name));
}

Sample output fragment:

./.profiles/sehe/.opera/icons/cache/g_0000
./.profiles/sehe/.opera/icons/cache/g_0000/opr00002.tmp
./.profiles/sehe/.opera/icons/cache/g_0000/opr00003.tmp
./.profiles/sehe/home/sehe/.mozilla
fts_children: Permission denied
./.vbox-sehe-ipc/lock
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added proper `errno` handling there too. – sehe Sep 26 '12 at 21:01
  • thank you. This was actually just a program to see if i knew how to use it clearly i did not. What I don't understand is why the files don't print out, only the directories do and why can i not the traverse the linked list (the commented while loop)? – tpar44 Sep 26 '12 at 21:21
  • @tpar44 Erm, yeah I didn't pay it much attention because you didn't ask anything about it. I have **now fixed that part** of the code too (updated the answer) – sehe Sep 26 '12 at 22:10
  • Thank you so much! However, Can I ask one more thing of you? My aim wasn't to have someone else do the example for me, but rather learn how to use fts(3) as it looks like a a function I should know. My question is, why did you move the second while loop inside of the first one? I thought that the function function returned a linked list for everything read, am I mistaken? – tpar44 Sep 27 '12 at 00:30
  • @tpar44 I moved it inside the while loop, since the fts_children call was also there... It kinda made sense to iterate once for _each_ result you get from `fts_read` instead of just the last. – sehe Sep 27 '12 at 06:38
  • @tpar For more info, [`man fts(3)`](http://www.kernel.org/doc/man-pages/online/pages/man3/fts.3.html) looks to be a far finer source. FWIW: I vastly prefer the `*ftw` interfaces - which are callback based, see e.g. this answer http://stackoverflow.com/a/7672081/85371 – sehe Sep 27 '12 at 06:41