2
\$\begingroup\$

For part of a project, I was given this directive:

The find command will use the directory (dirent) function calls, but will require you to start at a directory and then determine if a file is a directory or a file and if it is a directory you will need to walk down that directory as well. You will need a method/algorithm to note the new directories and build a logical graph or thread out every directory to find if the filename is in that directory and display the "absolute" path to that file.

I thought the nftw() function would be optimal for this, and I believe it makes my application pretty robust and fast. On my Mac (ommiting 196 lines of filename output each):

$ time find ../../ -name "CMakeLists.txt"
real    0m0.337s
user    0m0.025s
sys 0m0.292s

$ time ./my_find ../../ -name "CMakeLists.txt"
real    0m0.326s
user    0m0.022s
sys 0m0.278s

find.c:

#define _XOPEN_SOURCE 1
#define _XOPEN_SOURCE_EXTENDED 1

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <ftw.h>
#include <unistd.h>

#ifndef streq
#define streq(x, y) (strcmp((x), (y)) == 0)
#endif

typedef enum {SUCCESS, INVALD_DIR, OPENDIR_ERR, NFTW_ERR} Return;

const char* str = NULL;

int check_path(const char* path)
{
    DIR* dir = opendir(path);
    if (dir)  // directory exists
    {
        closedir(dir);
        return 0;
    }
    else if (ENOENT == errno) // directory doesn't exist
        return INVALD_DIR;
    else // opendir failed for some other reason
        return OPENDIR_ERR;
}

int callback(const char *file, const struct stat *sb, int flag, struct FTW *s)
{
    int err = 0;
    const char *name = file + s->base;

    switch (flag)
    {
        case FTW_F:
            if (streq(name, str))
                puts(file);
            break;
        case FTW_D:
        case FTW_DNR:
        case FTW_SL:
        case FTW_NS:
            break; // continue as normal
        case FTW_DP:
        case FTW_SLN:
        default:
            err = NFTW_ERR; // error occured
            break;
    }

    return err;
}

int process(const char* path)
{
    int err = nftw(path, callback, getdtablesize(), 0);
    return err;
}

void usage(const char* prog_name)
{
    fprintf(stdout, "usage: %s [path] -name <name>\n", prog_name);
    exit(EXIT_SUCCESS);
}


int main(int argc, char **argv)
{
    if(argc < 4)
        usage(argv[0]);

    const char* path = argv[1];
    str = argv[3];

    int err = check_path(path);
    if (err)
    {
        fprintf(stderr, "%s: %s: %s\n", argv[0], path, strerror(errno));
        return err;
    }
    else
        process(path);

    return err;
}

Any suggestions?

\$\endgroup\$
1
\$\begingroup\$

I like your solution, it's clever & fast, however I can't help but feel like it goes against the spirit of the assignment. You've found a built in system call that greatly simplifies what it is your need to do, which is great, but in doing so you've delegated the algorithm, which may not be what is expected from the assignment.

The assignment reads as if it's expecting your to use things like opendir and readdir to iterate through the folders yourself. You would then be responsible for maintaining state, and writing the iteration algorithm.

-name or not -name

Your argument handling is also a little bit on the naive side. Should I really be able to run it like this:

./my_find ../../ -ThisArgumentIsNeverCheckedSoICanPutPrettyMuchAnything "CMakeLists.txt"

Why both having the second argument if you're not going to check it, it's just extra typing...

Wildcards

It's not really clear from the spec, however the built in 'find' command supports wildcards. Your current implementation doesn't appear to (you're only doing an exact match).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.