Reputation: 109
In this code, if I print token[0]
, it gives me the result home, if I print token[1]
, it gives me the result bilal
But what I want is to print the files after this path /home/bilal/Videos/folder1
, like the token[0]
should be /home/bilal/Videos/folder1/fileOne
, and token[1]
should be /home/bilal/Videos/folder1/fileTwo
/home/bilal/Videos/folder1
this path should be printed everytime but fileOne or fileTwo should addad to this path if I try to select token[0]
or token[1]
.
Given result
printf("%s", token[0]);
result---> home home home home
printf("%s", token[1]);
result----> bilal bilal bilal bilal
Required result
printf("%s", token[0]);
result----> /home/bilal/Videos/folder1/fileOne
printf("%s", token[1]);
result----> /home/bilal/Videos/folder1/fileTwo
Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
void listdir(const char *path, const char *name, int indent){
DIR *dir;
struct dirent *entry;
char dirPath[1024] = {0}, *token[100], *savePtr;
if (!(dir = opendir(name)))
return;
while ((entry = readdir(dir)) != NULL) {
char path_[PATH_MAX];
long pathLen = strlen(path);
char *separator = (path[pathLen - 1] == '/') ? "" : "/";
sprintf(path_, "%s%s%s", path, separator, entry->d_name);
if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(dirPath, sizeof(dirPath), "%s/%s", name, entry->d_name);
// printf("%*sDirectory: %s\n", indent, "", dirPath);
listdir(path_, dirPath, indent + 4);
token[0] = strtok_r(dirPath, "/\n", &savePtr);
for(int i=1; i<50; i++){
token[i] = strtok_r(NULL, "/\n", &savePtr);
}
if (token[0])
printf("%*s\n", indent, token[0]);
if (token[1])
printf("%*s\n", indent, token[1]);
}
closedir(dir);
}
int main(){
listdir("/home/bilal/Videos/folder1", "/home/bilal/Videos/folder1", 4);
return 0;
}
Upvotes: 2
Views: 398
Reputation: 84607
In order to recursively traverse a directory tree, you will need to refactor the parameters used by your function a bit. Using a listdir()
as a wrapper function to call your actual recursive function will allow you to pass the path to search in, while the actual function that you use for recursion will allocate a block of pointers and pass the address-of the pointer-to-pointer where you maintain a list of directories you have traversed. That way when you enter a new recursive call, the path component to that point is the last directory path stored in your collection.
This will allow you to change the return type for listdir()
to char**
and return the address for the allocated and sorted collection of file and directory names. That way you can make use of the file and directory names found (beyond just printing them).
While recursion is a good technique to use for traversing a directory tree, be aware that POSIX already provides the functions ftw()
and nftw()
that do just that. The nftw()
being a newer ftw()
that allows passing a callback into the traversal function. The benefit to the POSIX functions is they provide several methods to keep track of, and minimize, the number of file-descriptors open at once. That way you can ensure you don't exhaust the available descriptors on your system.
That said, for most trees, and on most systems, you can use a general recursive function without exhausting the file-descriptors available. Using the wrapper function (your listdir()), what I've found works is to pass in the path to search and a function-pointer allowing you to specify how you want your directory listing sorted.
Within listdir()
you then call a helper-function that does the actual recursive call, passing as parameters the address to the collection of pointers holding the paths traversed to date along with two pointers to size_t
pointing to the total number of pointers Available and the total number Used. The helper function will then handle the reallocation when used == available
. At each directory level in the recursion scheme, if the entry is a directory, you add that to your collection.
A (not so short) example using that scheme and allowing you to specify each ascending or descending sort of the output is as follows:
/*
* When compiling with -std=c11 / -std=c17, #define _DEFAULT_SOURCE for DT_DIR
* Add either -D_GNU_SOURCE or -D_DEFAULT_SOURCE to your compiler options on Linux,
* or just add the #define _GNU_SOURCE or #define _DEFAULT_SOURCE below.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <unistd.h>
#define INITDSZ 64 /* initial number of pointers to allocate */
#ifndef PATH_MAX /* fallback definition for PATH_MAX */
#define PATH_MAX 4096
#endif
/* file/dir name comparison - ascending (sort dirs first) */
int sort_ascending (const void *a, const void *b)
{
const char *pa = *(char * const *)a,
*pb = *(char * const *)b,
*hasdira = NULL,
*hasdirb = NULL;
while (*pa == '.') /* scan past "." and ".." */
pa++;
while (*pb == '.')
pb++;
hasdira = strchr (pa + 1, '/'); /* check for 2nd '/' */
hasdirb = strchr (pb + 1, '/');
if (hasdira && !hasdirb) /* sort dirs before files */
return -1;
else if (!hasdira && hasdirb)
return 1;
return strcmp (*(char * const *)a, *(char * const *)b);
}
/* file/dir name comparison - descending (sort dirs first) */
int sort_descending (const void *a, const void *b)
{
const char *pa = *(char * const *)a,
*pb = *(char * const *)b,
*hasdira = NULL,
*hasdirb = NULL;
while (*pa == '.') /* scan past "." and ".." */
pa++;
while (*pb == '.')
pb++;
hasdira = strchr (pa + 1, '/'); /* check for 2nd '/' */
hasdirb = strchr (pb + 1, '/');
if (hasdira && !hasdirb) /* sort dirs before files */
return -1;
else if (!hasdira && hasdirb)
return 1;
return strcmp (*(char * const *)b, *(char * const *)a);
}
/* listdir_read - recursive read of directory storing entires in contents.
* listdir_read recursively loops over directory entries beginning with
* the last directory entry in contents. nptrs is a pointer to the currently
* allocated number of pointers in contents and n is the current used number
* of pointers. storage is allocated for each file/directory and each entry
* added to contents to preserve entries for sorting, and each diretory is
* recursed into to gather subdirectory entries. reallocation occurs as
* needed.
*/
void listdir_read (char ***contents, size_t *nptrs, size_t *n)
{
char *path = (*contents)[*n - 1]; /* pointer to current path */
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(path))) { /* open/validate directory */
perror ("opendir-path not found");
return;
}
while ((entry = readdir(dir))) { /* loop over each entry */
char *name = entry->d_name;
size_t len = strlen (name),
pathlen = strlen (path),
entrylen = pathlen + len + 1; /* +1 for '/' */
if (*n + 1 == *nptrs) { /* realloc, preserving sentinel NULL */
void *tmp = realloc (*contents, 2 * *nptrs * sizeof **contents);
if (!tmp) { /* validate */
perror ("listdir_read() realloc-*contents");
return;
}
*contents = tmp; /* assign new block, zero pointers */
memset (*contents + *nptrs, 0, *nptrs * sizeof **contents);
*nptrs *= 2; /* update number of allocated pointers */
}
if (entry->d_type == DT_DIR) /* if "." or ".." skip */
if (!strcmp(name, ".") || !strcmp(name, ".."))
continue;
(*contents)[*n] = malloc (entrylen + 1); /* allocate storage */
if (!(*contents)[*n]) { /* validate */
perror ("listdir_read() malloc-(*contents)[*n]");
return;
}
sprintf ((*contents)[(*n)++], "%s/%s", path, name); /* fill entry */
if (entry->d_type == DT_DIR) /* if is directory, recurse */
listdir_read (contents, nptrs, n);
}
closedir (dir); /* close directory */
}
/* wrapper for listdir_read, takes path and qsort compare function.
* returns allocated/sorted pointers to entries on success, NULL otherwise.
*/
char **listdir (char *path, int (*cmp)(const void*, const void*))
{
size_t len, n = 0, nptrs = INITDSZ;
char **contents = calloc (nptrs, sizeof *contents); /* allocate nptrs */
if (!contents) { /* validate */
perror ("listdir() calloc-contents");
return NULL;
}
len = strlen (path);
contents[n] = malloc (len + 1); /* allocate storage for 1st entry */
if (!contents[n]) { /* validate */
perror ("listdir() malloc-contents[n]");
return NULL;
}
strcpy (contents[n++], path); /* copy path as first entry */
listdir_read (&contents, &nptrs, &n); /* call listdir_read */
qsort (contents, n, sizeof *contents, cmp); /* sort contents */
return contents;
}
/* read path provided as argv[1] or present working directory by default.
* sort ascending with directories sorted first by default, or descending
* if any agrv[2] provided.
*/
int main (int argc, char **argv) {
char path[PATH_MAX],
**contents = NULL;
if (argc > 1)
strcpy (path, argv[1]);
else
*path = '.', path[1] = 0;
if (argc > 2)
contents = listdir (path, sort_descending);
else
contents = listdir (path, sort_ascending);
if (contents) {
char **p = contents;
while (*p) { /* avoid printing leading "./" before file or directory names */
if (strlen (*p) > 2 && **p == '.' && *(*p+1) == '/')
puts (*p + 2);
else
puts (*p);
free (*p++); /* free entries */
}
free (contents); /* free pointers */
}
return 0;
}
(note: since the code above uses the POSIX DT_DIR
macro for comparing whether the current entry is a directory, you need to define either _GNU_SOURCE
or _DEFAULT_SOURCE
to ensure that is available)
The code expects the starting path to search to be provided as the first argument to the program in argv[1]
and sorts ascending by default or descending if argv[2]
is given (it doesn't matter what the second argument actually is -- just that it is given) When done frees all allocated memory and exits.
There are a number of variations on how you can tweak the approach, but after wroking with the recursive approach for a while, this seemed to how I was able to meet all the requirements I had for the traversal. Let me know if you have further questions.
Edit Simplifying To Only Print File/Directory Names
As mentioned below after your comment on just outputting the file or directory names, the original problem of "How to keep track of the last directory remains?". You can either declare a global array of PATH_MAX
size and manipulate the path in that global variable, or use the same scheme above to only store directory entries and not file and directory names.
The global manipulation itself is non-trivial. You will need to add/remove elements from that path based on your position in the directory tree. Each time you return from a recursive call, you will have to remove the last path component, treating the global array as a stack in essence. However, this complicates error handling as you would also need to distinguish between a normal return and a return on error.
It is just as easy to allocate for and store the directory names of each directory when you make your recursive call. The scheme above minimizes the number of allocation by allocating blocks of pointers instead of calling realloc()
for every directory. That is likely what caused a bit of "Complexity Shock" when you looked at it first. The nptrs
and n
are just the allocated
pointers and used
pointers that allows for your reference of the last path to complete your pathname and to know when to reallocate.
With all the sorting and filename storage removed, and with the recursive function simply outputting the current file or directory name, you would have the following. main()
is greatly simplified, but still takes the path to search as the first argument (or searches from the current directory by default). listdir()
now takes only a single parameter -- the path to search, but still returns a pointer to the list of directories. You need not use the directories, but still need to save the return to free the memory. See if this simplified versions is easier to understand.
/*
* When compiling with -std=c11 / -std=c17, #define _DEFAULT_SOURCE for DT_DIR
* Add either -D_GNU_SOURCE or -D_DEFAULT_SOURCE on Linux
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#define INITDSZ 64 /* initial number of pointers to allocate */
#ifndef PATH_MAX /* fallback definition for PATH_MAX */
#define PATH_MAX 4096
#endif
/* listdir_read - recursive read of directory storing directory entires in contents.
* The last directory entry in contents is available through the stored contents.
* nptrs is a pointer to the currently allocated number of pointers in contents and n
* is the current used number of pointers. storage is allocated for each directory and
* each directory is added to contents to make the last path availabbe to the next call.
* each diretory is recursed into to gather subdirectory entries. reallocation occurs as
* needed.
*/
void listdir_read (char ***contents, size_t *nptrs, size_t *n)
{
char *path = (*contents)[*n - 1]; /* pointer to current path */
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(path))) { /* open/validate directory */
perror ("opendir-path not found");
return;
}
while ((entry = readdir(dir))) { /* loop over each entry */
char *name = entry->d_name;
size_t len = strlen (name),
pathlen = strlen (path),
entrylen = pathlen + len + 1; /* +1 for '/' */
if (entry->d_type == DT_DIR) /* if "." or ".." skip */
if (!strcmp(name, ".") || !strcmp(name, ".."))
continue;
/* just print the current file/directory name */
printf ("%s/%s\n", path, name);
/* handle just the storage of the last directory name & recursive call */
if (entry->d_type == DT_DIR) {
if (*n + 1 == *nptrs) { /* realloc, preserving sentinel NULL */
void *tmp = realloc (*contents, 2 * *nptrs * sizeof **contents);
if (!tmp) { /* validate */
perror ("listdir_read() realloc-*contents");
return;
}
*contents = tmp; /* assign new block, zero pointers */
memset (*contents + *nptrs, 0, *nptrs * sizeof **contents);
*nptrs *= 2; /* update number of allocated pointers */
}
(*contents)[*n] = malloc (entrylen + 1); /* allocate storage */
if (!(*contents)[*n]) { /* validate */
perror ("listdir_read() malloc-(*contents)[*n]");
return;
}
/* save last directory name */
sprintf ((*contents)[(*n)++], "%s/%s", path, name); /* fill entry */
listdir_read (contents, nptrs, n); /* recursive call */
}
}
closedir (dir); /* close directory */
}
/* wrapper for listdir_read, takes path to search and outputs each file/directory.
* returns allocated/sorted pointers to entries on success, NULL otherwise.
* caller is responsible for freeing the stored paths and pointers.
*/
char **listdir (char *path)
{
size_t len, n = 0, nptrs = INITDSZ;
char **contents = calloc (nptrs, sizeof *contents); /* allocate nptrs */
if (!contents) { /* validate */
perror ("listdir() calloc-contents");
return NULL;
}
len = strlen (path);
contents[n] = malloc (len + 1); /* allocate storage for 1st entry */
if (!contents[n]) { /* validate */
perror ("listdir() malloc-contents[n]");
return NULL;
}
strcpy (contents[n++], path); /* copy path as first entry */
listdir_read (&contents, &nptrs, &n); /* call listdir_read */
return contents;
}
/* read path provided as argv[1] */
int main (int argc, char **argv) {
char path[PATH_MAX],
**contents = NULL;
if (argc > 1)
strcpy (path, argv[1]);
else
*path = '.', path[1] = 0;
contents = listdir (path);
for (int i = 0; contents[i]; i++)
free (contents[i]); /* free entries */
free (contents); /* free pointers */
return 0;
}
Let me know if you have further questions.
Super Simple No Allocation Approach Using Global Array
After removing the sort and file storage, the process using the pointer-to-pointer to store directories visited during traversal of the directory tree is still a bit complicated. You can simplify further using a global array of PATH_MAX
size to append and remove directories from as you recursively traverse the tree. This greatly simplifies the problem, but also greatly limits the usefulness as you are limited to doing only what you do in listdir()
to the files. There are no allocations needed, so this should be about as simple as you can make it and still have it work.
The updated code is:
/*
* When compiling with -std=c11 / -std=c17, #define _DEFAULT_SOURCE for DT_DIR
* Add either -D_GNU_SOURCE or -D_DEFAULT_SOURCE on Linux
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#define INITDSZ 64 /* initial number of pointers to allocate */
#ifndef PATH_MAX /* fallback definition for PATH_MAX */
#define PATH_MAX 4096
#endif
char path[PATH_MAX] = ""; /* global array to store last path */
void listdir (void)
{
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(path))) { /* open/validate directory */
perror ("opendir-path not found");
return;
}
while ((entry = readdir(dir))) { /* loop over each entry */
char *name = entry->d_name;
if (entry->d_type == DT_DIR) /* if "." or ".." skip */
if (!strcmp(name, ".") || !strcmp(name, ".."))
continue;
/* just print the current file/directory name */
printf ("%s/%s\n", path, name);
/* handle just the storage of the last directory name & recursive call */
if (entry->d_type == DT_DIR) {
char *p;
strcat (path, "/");
strcat (path, name);
listdir(); /* recursive call */
/* locate last '/' after return from recursive call */
if ((p = strrchr (path, '/'))) {
if (p != path) /* if it isn't the beginning of path */
*p = 0; /* overwrite last '/' with nul-terminating char */
}
}
}
closedir (dir); /* close directory */
}
/* read path provided as argv[1] */
int main (int argc, char **argv) {
if (argc > 1)
strcpy (path, argv[1]);
else
*path = '.', path[1] = 0;
listdir();
}
Once you understand how this is using the array to hold the paths, and how the path is managed, then you should be able to work though the other examples and see how they add functionality by storing the entries (which allows sorted output, etc...)
Let me know if you have questions.
Upvotes: 2