Readdir() in a sequential behavior - c

int indent = 0;
int listDir(const char* dirname){
DIR* dir;
struct dirent* d;
if(!(dir = opendir(dirname)))
return -1;
while((d = readdir(dir)) != NULL){
if(strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0 ){
continue;
}
else if(d->d_type != DT_DIR){ // Any except folders.
printf("%*s- %s:%ld\n", indent, "", d->d_name, d->d_ino);
}
else if(d->d_type == DT_DIR){ // Folders only
printf("%*s[%s]\n", indent, "", d->d_name);
char path[1024];
snprintf(path, sizeof(path), "%s/%s", dirname, d->d_name);
indent +=2;
listDir(path);
indent -=2;
}
This function works just fine, but the only thing is that it outputs the following result as an example:
I need the output to be the container folder, files and then folders. The folders should be at the end of the list. For example, the above output should be:

I'd say you have two options:
Insert all of the readdir() results into a sorted data structure (or just put them in some array and sort it). And - I mean sorted in the sense of "file < directory and no other order".
Read all of the entries twice - once, ignore all the subdirectories and print just the files; then use rewinddir(), then read all of the entries again, ignoring all of the regular files and only printing the subdirectories.
Option 2 is probably simpler - but with more library calls and system calls.

Related

C: Providing regular expression as argument to open() ?

Is there a way to open a file with the open() without knowing its full name?
The linux shell provides an easy way to do that (in some sense), by accepting regular expressions as input.
For example, if you have a folder containing the files:
a.out file1 file2 file3 file4 file.txt test
and you want to list only the files with the prefix file you can do so by:
$ ls file*
file1 file2 file3 file4 file.txt
Or:
$ ls file[1-9]
file1 file2 file3 file4
To list only numbered files and so on...
I need to open the same file whenever my program launches.
The problem is, the file it needs to open is of the form: X*Y, meaning it starts with an X and ends with Y, but it could be anything in between.
For example, it could be X-Toshiba_12.45y9-Y, or it might be X-Dell-5.44s-Y.
I want to be able to open this file without having to consider the model.
The file may reside with some other files in that folder, but the X prefix and Y postfix are unique.
I could iterate the files in that folder and try to find my file by matching strings, but I'd rather avoid it.
Is there a way to provide open() with a regular expression somehow?
These are not regular expressions! You are talking about glob patterns.
You can use the POSIX.1-2001 glob() function to expand a glob pattern (like *.* or foo-*.?a* or *.[a-z]* and so on) to an array of filenames/pathnames that match the given pattern (starting at the current working directory, unless the pattern specifies an absolute path). This is basically what most shells use when they expand file name patterns.
If you were hell-bent on using regular expressions to specify file names (say, you need find-type behaviour, but with regular expressions), use SUSv4 nftw() function to traverse a directory tree -- it even works with the corner cases, like fewer descriptors than tree depth, or files modified, renamed, or moved while tree traversal --, and POSIX regex functions to filter on the file names. Note: regcomp() and regexec() etc. are built-in to POSIX.1-2001 -supporting C libraries, and that includes just about all current C library implementations for Linux. No external libraries are needed at all.
It makes me very sad to see example code using opendir()/readdir() to traverse a directory tree, when nftw() is available and much smarter and more robust. Just define _XOPEN_SOURCE 700 and _POSIX_C_SOURCE 200809L to get all these nice features in Linux and many *BSD variants, too.
Check this example
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
int
startswith(const char *const haystack, const char *const needle)
{
size_t haystackLength;
size_t needleLength;
if ((haystack == NULL) || (needle == NULL))
return 0;
haystackLength = strlen(haystack);
needleLength = strlen(needle);
if (haystackLength < needleLength)
return 0;
return (memcmp(haystack, needle, needleLength) == 0);
}
int
endswith(const char *const haystack, const char *const needle)
{
size_t haystackLength;
size_t needleLength;
if ((haystack == NULL) || (needle == NULL))
return 0;
haystackLength = strlen(haystack);
needleLength = strlen(needle);
if (haystackLength < needleLength)
return 0;
return (memcmp(haystack + haystackLength - needleLength, needle, needleLength) == 0);
}
void
searchdir(const char *const directory, const char *const starts, const char *const ends)
{
DIR *dir;
struct dirent *entry;
dir = opendir(directory);
if (dir == NULL)
return;
while ((entry = readdir(dir)) != NULL)
{
struct stat statbuf;
char filepath[PATH_MAX];
size_t length;
const char *name;
name = entry->d_name;
if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0))
continue;
length = snprintf(filepath, sizeof(filepath), "%s/%s", directory, name);
if (length >= sizeof(filepath))
{
fprintf(stderr, "unexpected error\n");
closedir(dir);
return;
}
if (stat(filepath, &statbuf) == -1)
{
fprintf(stderr, "cannot stat `%s'\n", filepath);
continue;
}
/* if the entry is a directory, probably recures */
if (S_ISDIR(statbuf.st_mode) != 0)
saerchdir(filepath, starts, ends);
/* or just, continue? */
/* The file name does not match */
if ((startswith(name, starts) == 0) || (endswith(name, ends) == 0))
continue;
/* Do whatever you want with the file */
fprintf(stdout, "%s\n", filepath);
}
closedir(dir);
}
int
main(int argc, char **argv)
{
if (argc < 4)
{
fprintf(stderr, "usage: %s directory startpattern endpattern\n", argv[0]);
fprintf(stderr, "\tex. %s /home/${USER} X Y\n", argv[0]);
return -1;
}
searchdir(argv[1], argv[2], argv[3]);
return 0;
}
Do whatever you want with the file, can go from pushing it into a char * array, to passing a function pointer to the function and executing that function on the file path.

writing ls from scratch recursively

I am working on a simple project to implement "ls -R" from scratch. Whenever I run what I have, my program just keeps searching the root directory over and over again. What am I doing wrong?
void lsR(char dirName[]) {
/*
The recursive function call.
*/
DIR *dir;
struct dirent *directory;
struct stat fileStat;
char type;
char **nameList[MAX_RECURSIVE_FILES];
struct passwd *user;
int count = 0;
int i = 0;
printf("\n");
printf("./%s :\n", dirName);
printf("\n");
if ((dir = opendir(dirName)) == NULL) {
perror("opendir error:");
return;
}
while ((directory = readdir(dir)) != NULL) {
if (stat(directory->d_name, &fileStat) < 0) {
perror("fstat error:");
return;
}
if (fileStat.st_uid == 1) {
continue;
}
user = getpwuid(fileStat.st_uid);
printf("%s ", directory->d_name);
fileType(&fileStat, &type);
if ((type == 'd') && (count < MAX_RECURSIVE_FILES)) {
nameList[count] = malloc(sizeof(char)*MAX_STRING_LENGTH);
strncpy(nameList[count++], directory->d_name, MAX_STRING_LENGTH);
}
}
closedir(dir);
printf("\n");
for (i=0; i<count; i++) {
printf("Calling lsR on: %s\n", nameList[i]);
lsR(nameList[i]);
}
}
When it executes, I get the following output:
"./. :
., .., ... all other files in my current working directory ....
./. :
., .., ... all other files in my current working directory...
"
Among the list of files in the current directory you've noticed . and .. The first one is a hardlink to the current directory and the second one to the parent directory. So when you recurse through your dir entries you will want to skip those two. Otherwise the first directory you will recurse into will be ., in other words the directory you've just gone through.
This is the reason of your program current behavior, but once you fix that you will run into the issue lurker mentioned in his answer.
Additional notes :
Are you sure about the char **nameList[MAX_RECURSIVE_FILES]; variable? Seems to me you want an array of char * not an array of char **.
Are you aware you can use the S_ISDIR macro on the st_mode field of your stat struct, in order to check that the current file is not a directory instead of your custom function?
You need to include the path relative to your program's current directory. Each nameList element will need to be dirName + "/" + directory->d_name.
If you started out calling lsR on the local directory, ./foo and foo has directory named bar under it, then to open bar you need to open ./foo/bar since your program is running from the directory represented by ..

C: <sys/stat.h> functions S_ISLNK, S_ISDIR and S_ISREG behaving oddly?

The code this is taken from compiles fine. It prints file names in a directory with the option of a letter in front of it: either a d,f,l, or o depending on their file type (o for other). However, I tested it on the directory /etc/network which has a symbolic file called run and it appeared as d? I've tried re-arranging the order of the if-statements too, but that gives an unsatisfactory output too. Am I using it incorrectly?
while ((ent = readdir (dp)) != NULL) {
lstat(ent->d_name, &st);
if (col){
if(S_ISDIR(st.st_mode)){
printf("d\t");
}
else if (S_ISREG(st.st_mode)){
printf("f\t");
}
else if (S_ISLNK(st.st_mode)){
printf("l\t");
}
else {
printf("o\t");
}
}
In this line: lstat(ent->d_name, &st);, dp->d_name contains only the name of the file, you need to pass the full path of the file to lstat() like this:
char full_path[512] = "DIR_PATH"; //make sure there is enough space to hold the path.
strcat(full_path, ent->d_name);
int col = lstat(full_path, &st);
BTW, S_ISDIR, S_ISLNK etc are POSIX macros, not functions.
This might work as an alternative solution:
if(col){
if(ent->d_type == DT_DIR)
printf("d ");
else if(ent->d_type == DT_LNK)
printf("l ");
else if(ent->d_type == DT_REG)
printf("f ");
}

Recursively find subdirectories and files

I want to retrieve all the files, directories and subdirectories contained within a given path, recursively. But I have a problem when my code reaches the second level (a directory within a directory): instead of opening the inner directory to search its contents, it throws an error. Here is what I have done:
void getFile(char *path)
{
DIR *dir;
struct dirent *ent;
if ((dir = opendir(path)) != NULL) {
/* print all the files and directories within directory */
while ((ent = readdir(dir)) != NULL) {
if((strcmp(ent->d_name,"..") != 0) && (strcmp(ent->d_name,".") != 0)){
printf ("%s", ent->d_name);
if(ent->d_type == DT_DIR){
printf("/\n");
getFile(ent->d_name);
}
else{
printf("\n");
}
} // end of if condition
} // end of while loop
closedir (dir);
}
Use the ftw(3) library function to recursively walk a file tree. It is quite standard.
You may also look into nftw and the MUSL libc source code for it. It is quite readable.
When you recursively call getFile you only call it with the only the name of the directory you just read. It's not the full path, which is what you need. You have to manage that yourself.
Something like this:
if(ent->d_type == DT_DIR)
{
if ((strlen(path) + strlen(ent->d_name) + 1) > PATH_MAX)
{
printf("Path to long\n");
return;
}
char fullpath[PATH_MAX + 1];
strcpy(fullpath, path);
strcat(fullpath, "/");
strcat(fullpath, ent->d_name); // corrected
getFile(fullpath);
}

How can I searches files in current dir and the files in directories that under current dir?

The function searches the files in current directory. If It accrosses a directory, It gets in and again searches for file except the current '.' and the previous '..' directory. But It doesnt work how I want.It does not get in the next directory.
int foo(char *currDir)
{
struct dirent *direntp;
DIR *dirp;
char currentDir[250];
if ((dirp = opendir(currDir)) == NULL)
{
perror ("Failed to open directory");
return 1;
}
//By Sabri Mev at GYTE
while ((direntp = readdir(dirp)) != NULL)
{
printf("%s\n", direntp->d_name);
if(direntp->d_type == DT_DIR)
{
if(strcmp(direntp->d_name,".") !=0 && strcmp(direntp->d_name,"..") != 0)
foo(direntp->d_name); //Recursive!
}
}
getcwd(currentDir,250);
printf("curr Dir : %s\n",currentDir );
while ((closedir(dirp) == -1) && (errno == EINTR)) ;
return 0;
}
Because your path is error.
try this
if(direntp->d_type == DT_DIR)
{
if(strcmp(direntp->d_name,".") !=0 && strcmp(direntp->d_name,"..") != 0)
{
sprintf(currentDir, "%s/%s", currDir, direntp->d_name);
foo(currentDir); //Recursive!
}
}
When you do the recursive call to foo() inside the loop, notice that what direntp->d_name contains is not the full path, but just the subdirectory name. You have to concatenate it with currDir and use the result to call foo().
For instance, if you're starting with foo("/home") and the first subdir is "root", you're calling recursively foo("root") when it should be foo("/home/root").
in direntp->d_name you access only the local directory name, it does not return the whole path
also getcwd function is deprecated. Use the ISO C++ conformant _getcwd instead (if you write in C++ off course).

Resources